|
58301
|
2058
|
11
|
2026-05-19T12:10:55.013688+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192655013_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true}]...
|
1961873295031593158
|
-1483003541378331465
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APP (-zst→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 5 m left100% <3• Tue 19 May 15:10:54•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov% Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLa Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToast# releases8 226 0• Messages• Bookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCl APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases...Google Cale...1...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58302
|
2059
|
15
|
2026-05-19T12:10:54.985582+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192654985_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true}]...
|
1961873295031593158
|
-1483003541378331465
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
PhostormVIewINavicareCodeLaravelKeractorTOOISWindowmelpFV faVsco.js?9 JY-20676-delete-report-related-objectsProiectC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phpW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReportActivitvServicet114 v#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtende136 %public function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormatti© AutomatedReportsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn1 usagepublic static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovzder')public function testHasCallTypeConference(array $callTypes. bool Sexpected): void{...}#DataProvider('hascalutvoelialerdataProviden'lLocal ChandesSheliLoo XChanges 4 tiles=.env.local app© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/AutomatedRepor.e^< → ® Side-by-side viewer -8 478e5b33 config/logging.phpDo not ignorec) JiminnyDebuqcommand.php app/Console/Commands"driver!=>'errorlod'.php locaing,.ohd contiaUnversioned Files 9 files'level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/laravel.log'),= env.nikilocal apo=.env.other appc) CanAccessAiReportsTest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/RK ravicon.ico oublidEids.txt ann1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn. ann/Console/Commands/Crm/HubsnotMLWEBHOOK_FILTERING IMPLEMENTATION.md aonl=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]« console (PROD]# console [euyA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn© Search.phpA 14 A102 V 73 ^ v 208211=212=2131219214E217Tx: AutovPlavaroundSo jiminny v021 A1 A18 V.2 Y6 ^SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42:SELECT * FROM users WHERE id = 143; # group 28select * from teams where id = 3143:select * from crm confiqurations where id = 500:select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:select * from activitles where crm confiquration 1d = 39and recordind state &'recorded' and duration > 60and status ='2025-12-01';SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;Socket tail to connect to host.address=(host=localhost|nort=3306/tvoe=orimarv). Connection refusedCascade0 Fix AutomatedRepohel"suppont Dally• om len100% 5• lue 19 May 10.10'04AutomatedReportsServiceTest ~+0 ..fix @AutomatedReportsservicelest.pnpo docker exec docker_lamp-t pnp artisan testFilter-testUpdateAskJ iminnyReportstatuorts/Automateakeportsservacerest.php -WARN No code coverage driver avallablema Lm F tu Bc n thn ng in ưng- ?Ask anvthina (*4L1¿ @sCodo CWS.1A0.01ditterenceCurrent version'driver' => 'errorlog','level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/laravel.log'),'custom_channel' => ['driver' => 'single','path' => storage_path('logs/custom.1og'),'level' => env('LOG_LEVEL', 'info').Tacts nacced. 228 (a minute aadWN Windsurf Toams 124-6 UTF.8io 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58303
|
2059
|
16
|
2026-05-19T12:10:57.962629+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192657962_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"bounds":{"left":0.66921544,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.7017952,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58302
|
NULL
|
NULL
|
NULL
|
|
58304
|
NULL
|
0
|
2026-05-19T12:11:26.863699+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192686863_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58301
|
NULL
|
NULL
|
NULL
|
|
58305
|
NULL
|
0
|
2026-05-19T12:11:28.219316+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192688219_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"bounds":{"left":0.66921544,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.7017952,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7287234,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"bounds":{"left":0.42652926,"top":0.415004,"width":0.29321808,"height":0.013567438},"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58306
|
2060
|
0
|
2026-05-19T12:11:57.123859+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192717123_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58307
|
2061
|
0
|
2026-05-19T12:11:58.491029+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192718491_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"bounds":{"left":0.66921544,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.7017952,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7287234,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"bounds":{"left":0.42652926,"top":0.415004,"width":0.29321808,"height":0.013567438},"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58305
|
NULL
|
NULL
|
NULL
|
|
58308
|
2060
|
1
|
2026-05-19T12:12:27.336729+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192747336_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58306
|
NULL
|
NULL
|
NULL
|
|
58309
|
2061
|
1
|
2026-05-19T12:12:28.740470+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192748740_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58305
|
NULL
|
NULL
|
NULL
|
|
58310
|
2061
|
2
|
2026-05-19T12:12:47.379677+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192767379_m2.jpg...
|
PhpStorm
|
Commit Changes
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodemelp°9 JY-20676-delete-re PhostormVIewINavicarecodemelp°9 JY-20676-delete-report-related-obProleteyC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTeAskAnytningPromptservice.ong© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReCommit Chanaes- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelestChangelist: Changes(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaV U tests/Unit/Services/Kiosk/AutomatedReports 1 filev C) AutomatedReoortsServiceTest.ono© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepubLic tunccion cesclranstorun= env.local© AutomatedReportsServiceActivitie:(C) AutomatedReportsServiceExtendepubulc tunccion cescoechedlalyUnversioned Files 9 files1 modified(c AutomatedRenortsServiceSieldbatpublic tunccion cestbecheclalyCommit Message(c) AutomatedRenortsServiceSormattiJY-20676 code revlew suggestions© AutomatedReportsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnpublic static function transfo#Uatarrovzder nascalui voeloipublic function testhascalltvo- Diff#DataProvider('hascalurvoeliaLocal ChandecSheliChanges 4 tiles=.env.local app© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Automate@ JiminnyDebugCommand.php app/Console/Commands8 478e5b33nrivate fuphp logaina.phd contiaUnversioned Files 9 files= env.nikilocal apo=.env.other appD: AutomateI@¢→Side-by-side viewerDo notignore@478e5b33 tests/…./Automatprevale tunctson getoervece$mockUserRepository = null,$mockStageRepository = null$mockTeamRepository = null,): AutomatedReportsService {return new AutomatedReportsService(($mockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class($mockUserRepository ?? $this->createMoc(SmockStageRepository ?? Sthis->createMoc) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/Rsth1s->createmockDealstaqesserv1ce::claK ravicon.ico oublidEids.txt ann1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn. ann/Console/Commands/Crm/HubsnotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodschis->creacerock.vealscagesservice..ctass)Sthis->createMock(RecipientsService::class).sth1s->createMockAutomatedReportsRepos1tory::class)sthis->createMockWebhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class).sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):=custom.log= laravel.log4 SF jiminny@localhost] X4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepoTx: AutovHaygroundSo jiminny vaalbl8XL XbAAuthor:Amend commitSian-off commitC': # 1695Commit checksUpdate copvriahi÷39Peformat code025-12-004Rearrange codeOpuimize imporis-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWS.1AHighlight worasbrimarv. Connection refusedo olterencesCurrent versionprivate function getService$mockUserRepository = null,$mockStageRepository = null,$mockTeamRepository = null,Current versionэпосккеро = null,): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class(SmockUserRepository ?? Sthis->createMod(SmockStageRepository ?? Sthis->createMoprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.SmockRena = null.Cancel): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMockWebhook::class)Sthis-›createMock(Dispatcher::class)sthis->createrockctivitviivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis-›reateMockAutomatedRenortsRenository::class):SmockReno->exnects(Sthis->never())->method('undate'):Scervice = Sthic->ae+ServiceimoSmockRenn)Sthis->eynec+Fycention/TnvalidAnaumentFxcention.•clacs)•hel"suppont Dally • 3m len100% 5• Tue 19 May 15:12:46+O •fix @AutomatedReportsservicelest.pnpfilter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =WARN No code coverage driver avallablehab ling with mising.-6 ditterencesPo 4 space....
|
NULL
|
6446290116111577392
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicarecodemelp°9 JY-20676-delete-re PhostormVIewINavicarecodemelp°9 JY-20676-delete-report-related-obProleteyC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTeAskAnytningPromptservice.ong© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReCommit Chanaes- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelestChangelist: Changes(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaV U tests/Unit/Services/Kiosk/AutomatedReports 1 filev C) AutomatedReoortsServiceTest.ono© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepubLic tunccion cesclranstorun= env.local© AutomatedReportsServiceActivitie:(C) AutomatedReportsServiceExtendepubulc tunccion cescoechedlalyUnversioned Files 9 files1 modified(c AutomatedRenortsServiceSieldbatpublic tunccion cestbecheclalyCommit Message(c) AutomatedRenortsServiceSormattiJY-20676 code revlew suggestions© AutomatedReportsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnpublic static function transfo#Uatarrovzder nascalui voeloipublic function testhascalltvo- Diff#DataProvider('hascalurvoeliaLocal ChandecSheliChanges 4 tiles=.env.local app© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Automate@ JiminnyDebugCommand.php app/Console/Commands8 478e5b33nrivate fuphp logaina.phd contiaUnversioned Files 9 files= env.nikilocal apo=.env.other appD: AutomateI@¢→Side-by-side viewerDo notignore@478e5b33 tests/…./Automatprevale tunctson getoervece$mockUserRepository = null,$mockStageRepository = null$mockTeamRepository = null,): AutomatedReportsService {return new AutomatedReportsService(($mockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class($mockUserRepository ?? $this->createMoc(SmockStageRepository ?? Sthis->createMoc) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/Rsth1s->createmockDealstaqesserv1ce::claK ravicon.ico oublidEids.txt ann1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn. ann/Console/Commands/Crm/HubsnotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodschis->creacerock.vealscagesservice..ctass)Sthis->createMock(RecipientsService::class).sth1s->createMockAutomatedReportsRepos1tory::class)sthis->createMockWebhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class).sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):=custom.log= laravel.log4 SF jiminny@localhost] X4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepoTx: AutovHaygroundSo jiminny vaalbl8XL XbAAuthor:Amend commitSian-off commitC': # 1695Commit checksUpdate copvriahi÷39Peformat code025-12-004Rearrange codeOpuimize imporis-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWS.1AHighlight worasbrimarv. Connection refusedo olterencesCurrent versionprivate function getService$mockUserRepository = null,$mockStageRepository = null,$mockTeamRepository = null,Current versionэпосккеро = null,): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class(SmockUserRepository ?? Sthis->createMod(SmockStageRepository ?? Sthis->createMoprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.SmockRena = null.Cancel): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMockWebhook::class)Sthis-›createMock(Dispatcher::class)sthis->createrockctivitviivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis-›reateMockAutomatedRenortsRenository::class):SmockReno->exnects(Sthis->never())->method('undate'):Scervice = Sthic->ae+ServiceimoSmockRenn)Sthis->eynec+Fycention/TnvalidAnaumentFxcention.•clacs)•hel"suppont Dally • 3m len100% 5• Tue 19 May 15:12:46+O •fix @AutomatedReportsservicelest.pnpfilter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =WARN No code coverage driver avallablehab ling with mising.-6 ditterencesPo 4 space....
|
58305
|
NULL
|
NULL
|
NULL
|
|
58311
|
2060
|
2
|
2026-05-19T12:12:47.746679+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192767746_m1.jpg...
|
PhpStorm
|
Commit Changes
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Diff","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Changelist:","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Changelist:","depth":1,"on_screen":true,"role_description":"pop up button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Changes","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rollback...","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Refresh","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
703081820363533783
|
-4501936780600407771
|
click
|
hybrid
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APP (-zst→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 3 m left100% <3• Tue 19 May 15:12:47•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov% Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLa Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToast# releases8 226 0• Messages• Bookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCl APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases...Google Cale...1...
|
58306
|
NULL
|
NULL
|
NULL
|
|
58312
|
2060
|
3
|
2026-05-19T12:12:53.126189+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192773126_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [ne SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APP (-zst→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 3 m left100% <3• Tue 19 May 15:12:52•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov% Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLa Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToast# releases8 226 0• Messages• Bookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCl APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases...Google Cale...1...
|
NULL
|
-4961845057584011900
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [ne SlackFileEditViewGoHistoryWindowHelpAPPDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APP (-zst→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 3 m left100% <3• Tue 19 May 15:12:52•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov% Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLa Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToast# releases8 226 0• Messages• Bookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCl APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases...Google Cale...1...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58313
|
2061
|
3
|
2026-05-19T12:12:53.126192+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192773126_m2.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecode°9 JY-20676-delete-report PhostormVIewINavicarecode°9 JY-20676-delete-report-related-obProleteyC ActivityController.ong© ResolveCompanyNameByEmailTraitTeAskAnytningPromptservice.ongc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedRe- Kloskclass AutomatedReportsservicelest→ AutomatedReportsC) ActivitvTvpeServiceTest.oho(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMedia© AskJiminnyReportServiceTest.phppubLic tunccion cesclranstorun(C) AutomatedReportsCallbackService(C) AutomatedReportsServiceActivitie.pubulc tunccion cescoechedlaly(C) AutomatedReportsServiceExtendepublic tunccion cestbecheclaly(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormatti© AutomatedReportsServiceGetActivpublic static function transfo© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php#Uatarrovzder nascalui voeloi190 V.public function testhascalltvo©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn#DataProvider('hascalurvoeliaLocal ChandecSheliChanges 4 tiles=.env.local app8 478e5b33© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Automate@ JiminnyDebugCommand.php app/Console/Commandsnrivate fuphp logaina.phd contiaUnversioned Files 9 files= env.nikilocal apo=.env.other appD: Automatec) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/RK ravicon.ico oublidEids.txt and1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn. ann/Console/Commands/Crm/HubsnotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodhel"suppont Dally • 3m len100% 5• Tue 19 May 15:12:53© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] X4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+O •Tx: AutovHaygroundSo jiminny vfix @AutomatedReportsservicelest.pnpaalbl8XL XbACommit ChanaesChangelist: ChangesV U tests/Unit/Services/Kiosk/AutomatedReports 1 filev C AutomatedReportsServiceTest.onoAuthor:Amend commitfilter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho == env.localSian-off commitUnversioned Files 9 filesWARN No code coverage driver avallableC': # 1695Commit checksCommit MessageJY-20676 F1x testsliUpdate copvriahihab ling with mising.-÷39Peformat code025-12-004Rearrange codeOpuimize imporis-b914-4000-b083-5687b32b2956') = uuid;- Diff¿ @sCodo CWS.1AI@¢→Side-by-side viewerDo notignoreHighlight woras@ 478e5b33 tests/…./Automatprevale tunctson getoervece$mockUserRepository = null,$mockStageRepository = null$mockTeamRepository = null,): AutomatedReportsService {93Vreturn new AutomatedReportsService(($mockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class($mockUserRepository ?? $this->createMoc(SmockStageRepository ?? Sthis->createMobrimarv. Connection refusedo olterencesCurrent versionprivate function getService$mockUserRepository = null,$mockStageRepository = null,$mockTeamRepository = null,6 ditterencesCurrent versionэпосккеро = null,): AutomatedReportsService {private function getService(SmocklicenRenositonv = null.sth1s->createmockDealstaqesserv1ce::clareturn new AutomatedReportsServiced(SmockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class(SmockUserRepository ?? Sthis->createMod(SmockStageRepository ?? Sthis->createMo$mockStageRepository = nullSmockTeamPenositonv = null.SmockRena = null.Cancelschis->creacerock.vealscagesservice..ctass)Sthis->createMock(RecipientsService::class).): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockAutomatedReportsRepos1tory::class)sthis->createMockWebhook::class)sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class).Sthis->createMockWebhook::class)Sthis->createMock(Dispatcher::class)sthis->createrock.Plavoookcategorvreoosttorv..class)sthis->createrockctivitviivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):SmockReno = Sthis-›reateMockAutomatedRenortsRenository::class):SmockReno->exnects(Sthis->never())->method('undate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Scervice = Sthic->ae+ServiceimoSmockRenn)Sproperty->setValue(Sthis->service, $mockRepo):Sthis->eynec+Fycention/TnvalidAnaumentFxcention.•clacs)•Po 4 space....
|
NULL
|
-2646458289823254926
|
NULL
|
click
|
ocr
|
NULL
|
PhostormVIewINavicarecode°9 JY-20676-delete-report PhostormVIewINavicarecode°9 JY-20676-delete-report-related-obProleteyC ActivityController.ong© ResolveCompanyNameByEmailTraitTeAskAnytningPromptservice.ongc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedRe- Kloskclass AutomatedReportsservicelest→ AutomatedReportsC) ActivitvTvpeServiceTest.oho(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMedia© AskJiminnyReportServiceTest.phppubLic tunccion cesclranstorun(C) AutomatedReportsCallbackService(C) AutomatedReportsServiceActivitie.pubulc tunccion cescoechedlaly(C) AutomatedReportsServiceExtendepublic tunccion cestbecheclaly(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormatti© AutomatedReportsServiceGetActivpublic static function transfo© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php#Uatarrovzder nascalui voeloi190 V.public function testhascalltvo©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn#DataProvider('hascalurvoeliaLocal ChandecSheliChanges 4 tiles=.env.local app8 478e5b33© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Automate@ JiminnyDebugCommand.php app/Console/Commandsnrivate fuphp logaina.phd contiaUnversioned Files 9 files= env.nikilocal apo=.env.other appD: Automatec) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/RK ravicon.ico oublidEids.txt and1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn. ann/Console/Commands/Crm/HubsnotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodhel"suppont Dally • 3m len100% 5• Tue 19 May 15:12:53© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] X4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+O •Tx: AutovHaygroundSo jiminny vfix @AutomatedReportsservicelest.pnpaalbl8XL XbACommit ChanaesChangelist: ChangesV U tests/Unit/Services/Kiosk/AutomatedReports 1 filev C AutomatedReportsServiceTest.onoAuthor:Amend commitfilter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho == env.localSian-off commitUnversioned Files 9 filesWARN No code coverage driver avallableC': # 1695Commit checksCommit MessageJY-20676 F1x testsliUpdate copvriahihab ling with mising.-÷39Peformat code025-12-004Rearrange codeOpuimize imporis-b914-4000-b083-5687b32b2956') = uuid;- Diff¿ @sCodo CWS.1AI@¢→Side-by-side viewerDo notignoreHighlight woras@ 478e5b33 tests/…./Automatprevale tunctson getoervece$mockUserRepository = null,$mockStageRepository = null$mockTeamRepository = null,): AutomatedReportsService {93Vreturn new AutomatedReportsService(($mockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class($mockUserRepository ?? $this->createMoc(SmockStageRepository ?? Sthis->createMobrimarv. Connection refusedo olterencesCurrent versionprivate function getService$mockUserRepository = null,$mockStageRepository = null,$mockTeamRepository = null,6 ditterencesCurrent versionэпосккеро = null,): AutomatedReportsService {private function getService(SmocklicenRenositonv = null.sth1s->createmockDealstaqesserv1ce::clareturn new AutomatedReportsServiced(SmockTeamRepository ?? Sthis->createMoc$this->createMock(GroupRepository::class(SmockUserRepository ?? Sthis->createMod(SmockStageRepository ?? Sthis->createMo$mockStageRepository = nullSmockTeamPenositonv = null.SmockRena = null.Cancelschis->creacerock.vealscagesservice..ctass)Sthis->createMock(RecipientsService::class).): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockAutomatedReportsRepos1tory::class)sthis->createMockWebhook::class)sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class).Sthis->createMockWebhook::class)Sthis->createMock(Dispatcher::class)sthis->createrock.Plavoookcategorvreoosttorv..class)sthis->createrockctivitviivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):SmockReno = Sthis-›reateMockAutomatedRenortsRenository::class):SmockReno->exnects(Sthis->never())->method('undate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Scervice = Sthic->ae+ServiceimoSmockRenn)Sproperty->setValue(Sthis->service, $mockRepo):Sthis->eynec+Fycention/TnvalidAnaumentFxcention.•clacs)•Po 4 space....
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58314
|
2061
|
4
|
2026-05-19T12:13:16.192359+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192796192_m2.jpg...
|
PhpStorm
|
Code Analysis
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Code Analysis","depth":1,"bounds":{"left":0.35006648,"top":0.28411812,"width":0.14926861,"height":0.014365523},"on_screen":true,"value":"Code Analysis","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"bounds":{"left":0.35006648,"top":0.28411812,"width":0.032247342,"height":0.014365523},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?","depth":1,"bounds":{"left":0.35006648,"top":0.3048683,"width":0.14926861,"height":0.06783719},"on_screen":true,"help_text":"text/html","role_description":"text"}]...
|
-3887671579903540818
|
6652477153917186252
|
click
|
accessibility
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?...
|
58313
|
NULL
|
NULL
|
NULL
|
|
58315
|
2060
|
4
|
2026-05-19T12:13:16.195938+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192796195_m1.jpg...
|
PhpStorm
|
Code Analysis
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?
Cancel...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Code Analysis","depth":1,"on_screen":true,"value":"Code Analysis","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?","depth":1,"on_screen":true,"help_text":"text/html","role_description":"text"},{"role":"AXButton","text":"Cancel","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8845833150680500254
|
6652476604125589700
|
click
|
accessibility
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php contains problems.<br/>No errors and 14 warnings found.<br/>Would you like to review them?
Cancel...
|
58312
|
NULL
|
NULL
|
NULL
|
|
58316
|
2061
|
5
|
2026-05-19T12:13:18.248044+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192798248_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicareCodeKeractorWindowFV faVsco.j PhostormVIewINavicareCodeKeractorWindowFV faVsco.js?9 JY-20676-delete-report-related-objectsProiectC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendepublic function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormatti© AutomatedReportsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn1 usagepublic static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovder')npublic function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....#DataProvider('hascalutvoelialerDataProviden'lLooXChanges 4 tiles=.env.local app© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Aut@ JiminnyDebugCommand.php app/Console/CommandsDo not ignoreHighlight wordsy@ 478e5b33 tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phpprivate function getServiceSmocklicerRenositonv = nullliphp logaina.phd contiaUnversioned Files 9 filesSmockStaneRenositonv = nul1l= env.nikilocal apo=.env.other appc) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/RSmockTeamPenositonv = nul1lK ravicon.ico oublidEids.txt and1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn an/Console/Commands/Crm/HubspotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodD: AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? $this->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class))Sthis->createMock(DealStagesService::class)Sthis->createMock(RecipientsService::class).sth1s->createrockAutomatedReportsRepos1tory::class)Sthis->createMock(Webhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class)sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):hhlsuppont Dally • 2m len100% 5• Tue 19 May 15:13:18AutomatedReportsServiceTest ~=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+0 ..Tx: AutovSo jiminny vfix @AutomatedReportsservicelest.pnp© Search.phpaalbl8XL XbAA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28211filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =—2121219select * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable214E217hab ling with mising.-select * from activities where crm confiquration 1d = 39and recordind state ='recorded' and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWE.16Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused0.06 ditterencesCurrent versionprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMock(Webhook:: class)Sthis->createMock(Dispatcher::class)sthis->createrockctivitvivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis-reateMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('undate'):SmockRenn)Sthis->eynec+Fycention/TnvalidAnaumentFxcention.clacs)•Tacts naccod. 228 /2 minutes adolWN Windsurf Teamsio 4 spaces...
|
NULL
|
-908811751794481467
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicareCodeKeractorWindowFV faVsco.j PhostormVIewINavicareCodeKeractorWindowFV faVsco.js?9 JY-20676-delete-report-related-objectsProiectC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendepublic function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormatti© AutomatedReportsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn1 usagepublic static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovder')npublic function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....#DataProvider('hascalutvoelialerDataProviden'lLooXChanges 4 tiles=.env.local app© AutomatedReportsServiceTest.php tests/Unit/Services/Kiosk/Aut@ JiminnyDebugCommand.php app/Console/CommandsDo not ignoreHighlight wordsy@ 478e5b33 tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phpprivate function getServiceSmocklicerRenositonv = nullliphp logaina.phd contiaUnversioned Files 9 filesSmockStaneRenositonv = nul1l= env.nikilocal apo=.env.other appc) CanAccessAiReportsTlest.ono tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/RSmockTeamPenositonv = nul1lK ravicon.ico oublidEids.txt and1& raw sal querv sal annl@ SimulateWebhooksCommand.ohn an/Console/Commands/Crm/HubspotMLWEBHOOK_FILTERING IMPLEMENTATION.md aodD: AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? $this->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class))Sthis->createMock(DealStagesService::class)Sthis->createMock(RecipientsService::class).sth1s->createrockAutomatedReportsRepos1tory::class)Sthis->createMock(Webhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvivpeServ1ce::class)sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):hhlsuppont Dally • 2m len100% 5• Tue 19 May 15:13:18AutomatedReportsServiceTest ~=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+0 ..Tx: AutovSo jiminny vfix @AutomatedReportsservicelest.pnp© Search.phpaalbl8XL XbAA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28211filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =—2121219select * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable214E217hab ling with mising.-select * from activities where crm confiquration 1d = 39and recordind state ='recorded' and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWE.16Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused0.06 ditterencesCurrent versionprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMock(Webhook:: class)Sthis->createMock(Dispatcher::class)sthis->createrockctivitvivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis-reateMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('undate'):SmockRenn)Sthis->eynec+Fycention/TnvalidAnaumentFxcention.clacs)•Tacts naccod. 228 /2 minutes adolWN Windsurf Teamsio 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58317
|
2061
|
6
|
2026-05-19T12:13:21.647057+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192801647_m2.jpg...
|
PhpStorm
|
Push Commits to app
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.12699468,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JY-20676 fix tests","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.12699468,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"bounds":{"left":0.61269945,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"bounds":{"left":0.6236702,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jump to Source","depth":2,"bounds":{"left":0.63231385,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Details","depth":2,"bounds":{"left":0.64328456,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"bounds":{"left":0.72539896,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"bounds":{"left":0.7340425,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder","depth":4,"bounds":{"left":0.6203458,"top":0.33838788,"width":0.11103723,"height":0.017557861},"on_screen":true,"role_description":"text"}]...
|
-1385554397422724194
|
-6251551627548756588
|
visual_change
|
accessibility
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder...
|
58316
|
NULL
|
NULL
|
NULL
|
|
58318
|
2060
|
5
|
2026-05-19T12:13:30.191766+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192810191_m1.jpg...
|
PhpStorm
|
Push Commits to app
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder
AutomatedReportsServiceTest.php, class
AutomatedReportsServiceTest.php, class
For Commit and Push to non-protected branches, preview commits before push
Help
Push tags:
All
Current Branch
Cancel
Push
Push...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.41041666,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JY-20676 fix tests","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.41041666,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jump to Source","depth":2,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Details","depth":2,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsServiceTest.php, class","depth":5,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsServiceTest.php, class","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"For Commit and Push to non-protected branches, preview commits before push","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":1,"on_screen":true,"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Push tags:","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"All","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Current Branch","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Cancel","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Push","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Push","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3955437401923396134
|
-2083464119668312812
|
click
|
accessibility
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder
AutomatedReportsServiceTest.php, class
AutomatedReportsServiceTest.php, class
For Commit and Push to non-protected branches, preview commits before push
Help
Push tags:
All
Current Branch
Cancel
Push
Push...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58319
|
2061
|
7
|
2026-05-19T12:13:30.212936+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192810212_m2.jpg...
|
PhpStorm
|
Push Commits to app
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder
AutomatedReportsServiceTest.php, class
AutomatedReportsServiceTest.php, class
For Commit and Push to non-protected branches, preview commits before push
Help
Push tags:...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.19647606,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JY-20676 fix tests","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.19647606,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"bounds":{"left":0.61269945,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"bounds":{"left":0.6236702,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jump to Source","depth":2,"bounds":{"left":0.63231385,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Details","depth":2,"bounds":{"left":0.64328456,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"bounds":{"left":0.72539896,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"bounds":{"left":0.7340425,"top":0.31524342,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder","depth":4,"bounds":{"left":0.6203458,"top":0.33838788,"width":0.11103723,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsServiceTest.php, class","depth":5,"bounds":{"left":0.62666225,"top":0.35594574,"width":0.080119684,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsServiceTest.php, class","depth":4,"bounds":{"left":0.62666225,"top":0.35594574,"width":0.080119684,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"For Commit and Push to non-protected branches, preview commits before push","depth":1,"bounds":{"left":0.48304522,"top":0.6783719,"width":0.17287233,"height":0.019952115},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":1,"bounds":{"left":0.4820479,"top":0.7047087,"width":0.00930851,"height":0.027134877},"on_screen":true,"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Push tags:","depth":1,"bounds":{"left":0.49800533,"top":0.70790106,"width":0.031914894,"height":0.019952115},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2978293361437058562
|
-2083470716805196524
|
click
|
accessibility
|
NULL
|
JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects → origin : JY-20676-delete-report-related-objects
JY-20676 fix tests
Show Diff
Group By
Jump to Source
Show Details
Expand All
Collapse All
tests/Unit/Services/Kiosk/AutomatedReports 1 file, folder
AutomatedReportsServiceTest.php, class
AutomatedReportsServiceTest.php, class
For Commit and Push to non-protected branches, preview commits before push
Help
Push tags:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58320
|
2061
|
8
|
2026-05-19T12:13:31.597811+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192811597_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicareCodeWindowFV faVsco.js?9 JY-2 PhostormVIewINavicareCodeWindowFV faVsco.js?9 JY-20676-delete-report-related-objectsProiectC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): voidt...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendepublic function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv1 usagepublic static function transformMediaTypesDataProvider(: array{...}© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn#Uararrovzdernascalu.voeconterencevararrovder')npublic function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....#DataProvider('hascalutvoelialerDataProviden'SheliChanges 3 tiles=.env.local appDo not ignoreHighlight wordsy@ 478e5b33 tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phpphp logging.php contigv Unversioned Flles & tllegE .env.nikilocal appE envother appC CanAccessAiReportsTest.php tests/Unit/Policiesprivate function getServiceSmocklicerRenositonv = nullliSmockStaneRenositonv = nulllSmockTeamPenositonv = nul1lC) CreateMockAskJiminnvReoortResultCommand.oho aoo/Console/Commands/Rhravicon.ico public= ids.tyt andR raw sal querv.sal annC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/HubsnotMLWERHOOK_FILTERING_IMPLEMENTATION.md aodD: AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? $this->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class))Sthis->createMock(DealStagesService::class)Sthis->createMock(RecipientsService::class).sth1s->createrockAutomatedReportsRepos1tory::class)Sthis->createMock(Webhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvlvpeServ1ce::class).sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):helsuppont Dally • 2m len100% 5• Tue 19 May 15:13:31AutomatedReportsServiceTest ~=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+0 ..Tx: AutovPlavaroundSo jiminny vfix @AutomatedReportsservicelest.pnp© Search.phpLalbl8 XL XbAA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =—2121219select * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable214E217hab ling with mising.-select * from activitles where crm confiquration 1d = 39and recordind state ='recorded' and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWE.16Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused* 6 differencesCurrent versionprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMock(Webhook:: class)Sthis-›createMock(Dispatcher::class)sthis->createrockctivitvivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class):SmockReno->expects(Sthis->neverOh->method('uodate'):i 1 file committedSmockRenn)JY-206/6 Tix testsEdit Commit Messade.Sthis->eynec+Fycention/TnvalidAnaumentFxcention.clacs)•Duchineio 4 spaces...
|
NULL
|
-7123332844972921577
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicareCodeWindowFV faVsco.js?9 JY-2 PhostormVIewINavicareCodeWindowFV faVsco.js?9 JY-20676-delete-report-related-objectsProiectC ActivityController.ong© AskAnythingController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php Xc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpc) UpdateCrmDataResolverlest.phoW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.ohoclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): voidt...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackServicepublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendepublic function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv1 usagepublic static function transformMediaTypesDataProvider(: array{...}© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhn#Uararrovzdernascalu.voeconterencevararrovder')npublic function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....#DataProvider('hascalutvoelialerDataProviden'SheliChanges 3 tiles=.env.local appDo not ignoreHighlight wordsy@ 478e5b33 tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phpphp logging.php contigv Unversioned Flles & tllegE .env.nikilocal appE envother appC CanAccessAiReportsTest.php tests/Unit/Policiesprivate function getServiceSmocklicerRenositonv = nullliSmockStaneRenositonv = nulllSmockTeamPenositonv = nul1lC) CreateMockAskJiminnvReoortResultCommand.oho aoo/Console/Commands/Rhravicon.ico public= ids.tyt andR raw sal querv.sal annC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/HubsnotMLWERHOOK_FILTERING_IMPLEMENTATION.md aodD: AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? $this->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class))Sthis->createMock(DealStagesService::class)Sthis->createMock(RecipientsService::class).sth1s->createrockAutomatedReportsRepos1tory::class)Sthis->createMock(Webhook::class)Sth1s->createMockD1spatcher::class).Sth1s->createrockActiv1tvlvpeServ1ce::class).sthis->createrock.Plavoookcategorvreoosttorv..class)SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class)•SmockReno->exnects(Sthis->neverOh->method('uodate'):Sreflection = newReflectionClassAutomatedRenortsService::class):Sproperty = Sreflection->getProperty('automatedReportsRepository');Snronentv->setAccecsihle(tnue)•Sproperty->setValue(Sthis->service, $mockRepo):helsuppont Dally • 2m len100% 5• Tue 19 May 15:13:31AutomatedReportsServiceTest ~=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascadeA console [STAGING]C) CoachinaFeedbackCoachUserin.onp0 Fix AutomatedRepo+0 ..Tx: AutovPlavaroundSo jiminny vfix @AutomatedReportsservicelest.pnp© Search.phpLalbl8 XL XbAA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =—2121219select * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable214E217hab ling with mising.-select * from activitles where crm confiquration 1d = 39and recordind state ='recorded' and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;¿ @sCodo CWE.16Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused* 6 differencesCurrent versionprivate function getService(SmocklicenRenositonv = null.$mockStageRepository = nullSmockTeamPenositonv = null.): AutomatedReportsService {return new AutomatedReportsService((SmockTeamRepository ?? $this->createMock(TeamRepository::class))Sthis->createMock(GroupRepository::class)(SmockUserRepository ?? Sthis->createMock(UserRepository::class))(SmockStageRepository ?? Sthis->createMock(StageRepository::class)).Sthis->createMock(DealStagesService::class).sth1s->createMockRec1p1entsServ1ce::class)(SmockRepo ?? Sthis->createMock(AutomatedReportsRepository::class))Sthis->createMock(Webhook:: class)Sthis-›createMock(Dispatcher::class)sthis->createrockctivitvivoeService:.class)sthis->createMock(PlaybookCategoryRepos1tory::class),SmockReno = Sthis->createMockAutomatedRenortsRenositorv::class):SmockReno->expects(Sthis->neverOh->method('uodate'):i 1 file committedSmockRenn)JY-206/6 Tix testsEdit Commit Messade.Sthis->eynec+Fycention/TnvalidAnaumentFxcention.clacs)•Duchineio 4 spaces...
|
58319
|
NULL
|
NULL
|
NULL
|
|
58321
|
2061
|
9
|
2026-05-19T12:13:34.835183+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192814835_m2.jpg...
|
PhpStorm
|
Push Rejected
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Help
Push Rejected
text/html
text/html
text/html
P Help
Push Rejected
text/html
text/html
text/html
Push of the current branch "JY-20676-delete-report-related-objects" was rejected. Remote changes need to be merged before pushing.
Remember the update method and update silently in the future. Change this later in Settings | Version Control.
Cancel
Rebase
Merge...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Help","depth":1,"bounds":{"left":0.43351063,"top":0.4301676,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Push Rejected","depth":1,"bounds":{"left":0.44680852,"top":0.28092578,"width":0.120678194,"height":0.014365523},"on_screen":true,"value":"Push Rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"bounds":{"left":0.44680852,"top":0.28092578,"width":0.032247342,"height":0.014365523},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Push of the current branch \"JY-20676-delete-report-related-objects\" was rejected. Remote changes need to be merged before pushing.","depth":1,"bounds":{"left":0.44680852,"top":0.30167598,"width":0.120678194,"height":0.054269753},"on_screen":true,"help_text":"text/html","role_description":"text"},{"role":"AXCheckBox","text":"Remember the update method and update silently in the future. Change this later in Settings | Version Control.","depth":1,"bounds":{"left":0.44547874,"top":0.36552274,"width":0.12200798,"height":0.04708699},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel","depth":1,"bounds":{"left":0.47041222,"top":0.42777336,"width":0.028590426,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rebase","depth":1,"bounds":{"left":0.50299203,"top":0.42777336,"width":0.03125,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Merge","depth":1,"bounds":{"left":0.5382314,"top":0.42777336,"width":0.02925532,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8575235925256567213
|
8023401888499584087
|
visual_change
|
accessibility
|
NULL
|
Help
Push Rejected
text/html
text/html
text/html
P Help
Push Rejected
text/html
text/html
text/html
Push of the current branch "JY-20676-delete-report-related-objects" was rejected. Remote changes need to be merged before pushing.
Remember the update method and update silently in the future. Change this later in Settings | Version Control.
Cancel
Rebase
Merge...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58322
|
2060
|
6
|
2026-05-19T12:13:40.651850+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192820651_m1.jpg...
|
PhpStorm
|
Push Rejected
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Help
Push Rejected
text/html
text/html
text/html
P Help
Push Rejected
text/html
text/html
text/html
Push of the current branch "JY-20676-delete-report-related-objects" was rejected. Remote changes need to be merged before pushing.
Remember the update method and update silently in the future. Change this later in Settings | Version Control.
Cancel
Rebase...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Help","depth":1,"on_screen":true,"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Push Rejected","depth":1,"on_screen":true,"value":"Push Rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Push of the current branch \"JY-20676-delete-report-related-objects\" was rejected. Remote changes need to be merged before pushing.","depth":1,"on_screen":true,"help_text":"text/html","role_description":"text"},{"role":"AXCheckBox","text":"Remember the update method and update silently in the future. Change this later in Settings | Version Control.","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rebase","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
2244444790016361772
|
8032413485801360471
|
click
|
accessibility
|
NULL
|
Help
Push Rejected
text/html
text/html
text/html
P Help
Push Rejected
text/html
text/html
text/html
Push of the current branch "JY-20676-delete-report-related-objects" was rejected. Remote changes need to be merged before pushing.
Remember the update method and update silently in the future. Change this later in Settings | Version Control.
Cancel
Rebase...
|
58318
|
NULL
|
NULL
|
NULL
|
|
58323
|
2060
|
7
|
2026-05-19T12:13:42.949839+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192822949_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER* [new b SlackFileEditViewGoHistoryWindowHelpDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APPAPP (-zsl→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 2m left100% <3• Tue 19 May 15:13:42•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov. Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLe Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToastGoogle Cale...# releases8 226 0MessagesBookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCI APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases+......
|
NULL
|
2353086228930516217
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER* [new b SlackFileEditViewGoHistoryWindowHelpDOCKER* [new branch]ea3519f0e2..177ea29b8d• ₴1DEV (docker)JY-20943-fix-calendar-events₴82APPAPP (-zsl→> origin/JY-24-> origin/mastefront-end/src/components/AiReports/Manage/useReportsGrid.jsfront-end/src/components/DealInsights/Modals/AskAnythingModal.vuefront-end/src/components/DealInsights/Modals/__tests__/AskAnythingModal.spec.jsfront-end/src/components/shared/AskAnything/__tests__/AskAnythingSettingsDrawer.spec.js4 files changed,109 insertions(+), 24 deletions(-)create mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModclukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obiOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse "git push"to publish your local commits)Changes notstaged for commit:Cuse"git add<file>..."to update what will becommitted)Cuse "git restore<files..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 88,done.Counting objects: 100% (62/62), done.Deltacompression using up to 8 threadsCompressing objects: 100% (33/33), done.motti 3a decta 25), ressea) (deita 0), 5.-reNse/S, done.remote: Resolving deltas: 100% (25/25), completed with 24 local objects.To github.com:jiminny/app.git6865d8c2c2..478e5b33d8JY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•Morelall• Support Daily • 2m left100% <3• Tue 19 May 15:13:42•ED→Jiminny ...scnicrat# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...Describe what you are looking for. Direct messagesFo Nikolay Yankov. Galya Dimitrova€. Vasil VasilevP. Aneliya Angelovaã. Stefka StoyanovaStoyan TomovLe Todor Stamatov "NMario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevLukas Kovalik y... Ol:: AppsJira CloudToastGoogle Cale...# releases8 226 0MessagesBookmarks+C FilesToday ~CircleCI APP 1:06 PIVTDeployment Successful!Project: appWhen:05/19/202610:06:23Tag:View JobGitHub APP2:37 PM2 new commits pushed to master bymihailmihaylovjiminnyd65632ff - JY-20941: Fix TI ai-call-scoringaccess177ea29b - Merge pull request #12102 fromjiminny/JY-20941-fix-ti-ai-call-scoring-accessjiminny/app | Added by GitHubCircleCI APP 3:00 PMDeployment Successful!Project: appWhen:05/19/202612:00:02Tag:View JobMessage #releases+......
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58324
|
2061
|
10
|
2026-05-19T12:13:42.949839+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779192822949_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormProiect vVIewINavicarecodeLaravelKeractorW PhostormProiect vVIewINavicarecodeLaravelKeractorWindowmelphhl"suppont Dally • 2m lenu100% 5• Tue 19 May 15:13:42FV faVsco.js?9 JY-20676-delete-report-related-objectsAutomatedReportsServiceTest ~C ActivitvController.pnpphp api.php© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTec) TimePerioditeratorlest.phoc) UpdateCrmDataResolverlest.phpW Internal- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.oho(C) AskJiminnvReportActivitvServicet© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackService(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtende(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnLocal ChandesSheliLooX© AskAnythingPromptService.php© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn0 Fix AutomatedRepo+O •© AskAnythingPromptDto.phpTx: AutovPlavaround© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php© Search.phpSo jiminny vaalbl8XL XbAfix @AutomatedReportsservicelest.pnpclass AutomatedReportsservicelest extends TestcaseA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}211—212filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =public function testGetMediaTypeFieldDataWithoutReport: void{...}selecc * rrom ceams where 10 = 51451select * from crm confiqurations where id = 500:select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable136 %public function testGetMediaTypeFieldDataWithReport: void{...}1 usagepublic static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovzder.n219214E217219221|Ty Eepory tatus no oe n eapting with ntsanehab ling with mising.-select * from activities where crm confiquration 1d = 39and recordind state =recorded'and duration > 60and status = 'combleted' and actual start time >='2025-12-01';SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....¿ @sCodo CWS.1A#DataProvider('hascalutvoelialerDataProviden'Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused0.0Changes 3 tiles=.env.local appSide-by-side viewerDo not ignoreHighlight words 154 difterences8 478e5b33 app/Console/ConCurrent versionphp logging.php contigv Unversioned Flles g tllesE .env.nikilocal app= env.other apoC CanAccessAiReportsTest.php tests/Unit/Policiesnamesnace Jiminny Console. Commands:namecnace liminnv Concolle Commandsuse Illuminate\Console\Commanduse Jiminny Models\Team;use Tlluminate Console Commandiuse Tlluminate Sunnont Cacadec Redic.use liminnv Modelc Team•C) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Rk ravicon.ico public= ids.txt aodR raw sal querv.sal annC) SimulateWebhooksCommand.ohn ann/Console/Commands/Crm/Hubsnot* Class JiminnyDebugCommano* Class JiminnyDebugCommand* @package Jiminnu\ Console\ Commands* @package Jiminny\Console\CommandsMLWERHOOK_FILTERING_IMPLEMENTATION.md aodclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug':procecced ssiqnacure ="niminny.debua saccion.public function handled: voidpublic function handled: void$team = Team: :find0rFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->argument('action'):if (Saction === 'redis-set') {var dumo(Susersids):Sthis->testRedisSeto:die:exitan# 1 file committedJY-20676 fix testsEdit Commit Messade.pyitan +Pushing..124-6UTF.8io 4 spaces...
|
NULL
|
-8354028367560659646
|
NULL
|
click
|
ocr
|
NULL
|
PhostormProiect vVIewINavicarecodeLaravelKeractorW PhostormProiect vVIewINavicarecodeLaravelKeractorWindowmelphhl"suppont Dally • 2m lenu100% 5• Tue 19 May 15:13:42FV faVsco.js?9 JY-20676-delete-report-related-objectsAutomatedReportsServiceTest ~C ActivitvController.pnpphp api.php© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTec) TimePerioditeratorlest.phoc) UpdateCrmDataResolverlest.phpW Internal- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.oho(C) AskJiminnvReportActivitvServicet© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackService(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtende(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnLocal ChandesSheliLooX© AskAnythingPromptService.php© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn0 Fix AutomatedRepo+O •© AskAnythingPromptDto.phpTx: AutovPlavaround© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php© Search.phpSo jiminny vaalbl8XL XbAfix @AutomatedReportsservicelest.pnpclass AutomatedReportsservicelest extends TestcaseA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}211—212filter=testUpdateAskJiminnyReportStatusorts/AutomatedReportsServicetest.oho =public function testGetMediaTypeFieldDataWithoutReport: void{...}selecc * rrom ceams where 10 = 51451select * from crm confiqurations where id = 500:select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:WARN No code coverage driver avallable136 %public function testGetMediaTypeFieldDataWithReport: void{...}1 usagepublic static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovzder.n219214E217219221|Ty Eepory tatus no oe n eapting with ntsanehab ling with mising.-select * from activities where crm confiquration 1d = 39and recordind state =recorded'and duration > 60and status = 'combleted' and actual start time >='2025-12-01';SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....¿ @sCodo CWS.1A#DataProvider('hascalutvoelialerDataProviden'Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused0.0Changes 3 tiles=.env.local appSide-by-side viewerDo not ignoreHighlight words 154 difterences8 478e5b33 app/Console/ConCurrent versionphp logging.php contigv Unversioned Flles g tllesE .env.nikilocal app= env.other apoC CanAccessAiReportsTest.php tests/Unit/Policiesnamesnace Jiminny Console. Commands:namecnace liminnv Concolle Commandsuse Illuminate\Console\Commanduse Jiminny Models\Team;use Tlluminate Console Commandiuse Tlluminate Sunnont Cacadec Redic.use liminnv Modelc Team•C) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Rk ravicon.ico public= ids.txt aodR raw sal querv.sal annC) SimulateWebhooksCommand.ohn ann/Console/Commands/Crm/Hubsnot* Class JiminnyDebugCommano* Class JiminnyDebugCommand* @package Jiminnu\ Console\ Commands* @package Jiminny\Console\CommandsMLWERHOOK_FILTERING_IMPLEMENTATION.md aodclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug':procecced ssiqnacure ="niminny.debua saccion.public function handled: voidpublic function handled: void$team = Team: :find0rFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->argument('action'):if (Saction === 'redis-set') {var dumo(Susersids):Sthis->testRedisSeto:die:exitan# 1 file committedJY-20676 fix testsEdit Commit Messade.pyitan +Pushing..124-6UTF.8io 4 spaces...
|
58321
|
NULL
|
NULL
|
NULL
|
|
58346
|
2062
|
0
|
2026-05-19T12:17:25.048830+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193045048_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5705708063369358983
|
-6869815717752515185
|
app_switch
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58347
|
2063
|
0
|
2026-05-19T12:17:25.043815+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193045043_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6311497628070158972
|
-1965396816615187057
|
app_switch
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58361
|
2063
|
5
|
2026-05-19T12:19:14.613591+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193154613_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormProiect vVIewINavicarecodeLaravelKeractorW PhostormProiect vVIewINavicarecodeLaravelKeractorWindowmelpFV faVsco.js°9 JY-20676-delete-report-related-objectsC ActivityController.ong© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn0 Fix AutomatedRepoc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpTx: AutovPlavaroundSo jiminny vc) UpdateCrmDataResolverlest.phpW Internal- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.oho© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php© Search.php021 A1 A18 V2 Y6 л Vclass Automatedkeportsservicelest extends TestcaseA 14 A102 V 73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackService2211—212public function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendeselect * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:136 %public function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv1 usage219214E217select * from activities where crm confiquration 1d = 39and recordind state =recorded'and duration > 60public static function transformMediaTypesDataProvider: array{...}#DataProvider('hascalutvoelialerDataProviden'Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refusedLocal Chandec© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnSheliand status = 'completed" and actual_start_time >= '2025-12-01';#Uararrovzdernascalu.voeconterencevararrovzder.n=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;190 V.public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....¿ @sCodo CWS.1A• Changes 3 tiles, updatina..env.local appTLO+→EDo not ignoreHighlight words 158 56ca84fa app/Console/Commands/JiminnyDebugCommand.phpCurrent versionc) JiminnyDebugCommand.php app/console/cophp logging.php contigv Unversioned Flles g tllesE .env.nikilocal app= env.other apoC CanAccessAiReportsTest.php tests/Unit/Policiesnamecnace liminnv Concolle Commands.namecnace liminnv Concolle Commands.use Illuminate\Console\Commanduse Jiminny Models\Team;use Tlluminatel Concole Command.C) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Ruse Illuminate\Support\Facades \Redis:use liminnv Modelc Toam•k ravicon.ico public= ids.tyt andRraw sal auerv.sal ann* Class JiminnyDebugCommano* Class JiminnyDebugCommandC) SimulateWebhooksCommand.ohn ann/Console/Commands/Crm/Hubsnot* @package Jiminnu\ Console\ CommandsMLWERHOOK_FILTERING_IMPLEMENTATION.md aod* @package Jiminnu\ Console\ Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug':protected Ssiqnature = '11minny:debua saction?':public function handled: voidnublic function handled): void$team = Team: :find0rFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->arqument('action'):var dumo(Susersids):die:exitanif Saction zz= 'redis-set') "return;AutomatedReportsServiceTest ~test.AutonatedReportsServiceTest.php -filter=testUpdateAskJiminnyReportStatus100% 2• Tue 19 May 15:19:14+0 ..+2-1Run#+ Skip0004 difterencesAPush reiectedorigin/JY-20676-delete-report-related-objects... vShow details in console124-6UTE.8Po 4 spaces...
|
NULL
|
3539906991002377531
|
NULL
|
click
|
ocr
|
NULL
|
PhostormProiect vVIewINavicarecodeLaravelKeractorW PhostormProiect vVIewINavicarecodeLaravelKeractorWindowmelpFV faVsco.js°9 JY-20676-delete-report-related-objectsC ActivityController.ong© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn0 Fix AutomatedRepoc) TimePerioditeratorlest.pho© AskAnythingPromptDto.phpTx: AutovPlavaroundSo jiminny vc) UpdateCrmDataResolverlest.phpW Internal- Klosk→ AutomatedReportsC) ActivitvTvpeServiceTest.oho© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php© Search.php021 A1 A18 V2 Y6 л Vclass Automatedkeportsservicelest extends TestcaseA 14 A102 V 73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28(C) AskJiminnvReportActivitvServicet#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}© AskJiminnyReportServiceTest.php(C) AutomatedReportsCallbackService2211—212public function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivitie.(C) AutomatedReportsServiceExtendeselect * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:136 %public function testGetMediaTypeFieldDataWithReport: void{...}(c AutomatedRenortsServiceSieldbat(c) AutomatedRenortsServiceSormattiCh AutomatedRenortsServiceGetActiv1 usage219214E217select * from activities where crm confiquration 1d = 39and recordind state =recorded'and duration > 60public static function transformMediaTypesDataProvider: array{...}#DataProvider('hascalutvoelialerDataProviden'Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refusedLocal Chandec© AutomatedReportsServiceGetPlayt©) AutomatedReportsServiceReportG©) AutomatedReportsServiceTest.php©) AutomatedReportsServiceValidate©) AutomatedReportsServiceValidatio(ei DociniontCorvicaToct nhnSheliand status = 'completed" and actual_start_time >= '2025-12-01';#Uararrovzdernascalu.voeconterencevararrovzder.n=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;190 V.public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....¿ @sCodo CWS.1A• Changes 3 tiles, updatina..env.local appTLO+→EDo not ignoreHighlight words 158 56ca84fa app/Console/Commands/JiminnyDebugCommand.phpCurrent versionc) JiminnyDebugCommand.php app/console/cophp logging.php contigv Unversioned Flles g tllesE .env.nikilocal app= env.other apoC CanAccessAiReportsTest.php tests/Unit/Policiesnamecnace liminnv Concolle Commands.namecnace liminnv Concolle Commands.use Illuminate\Console\Commanduse Jiminny Models\Team;use Tlluminatel Concole Command.C) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Ruse Illuminate\Support\Facades \Redis:use liminnv Modelc Toam•k ravicon.ico public= ids.tyt andRraw sal auerv.sal ann* Class JiminnyDebugCommano* Class JiminnyDebugCommandC) SimulateWebhooksCommand.ohn ann/Console/Commands/Crm/Hubsnot* @package Jiminnu\ Console\ CommandsMLWERHOOK_FILTERING_IMPLEMENTATION.md aod* @package Jiminnu\ Console\ Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug':protected Ssiqnature = '11minny:debua saction?':public function handled: voidnublic function handled): void$team = Team: :find0rFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->arqument('action'):var dumo(Susersids):die:exitanif Saction zz= 'redis-set') "return;AutomatedReportsServiceTest ~test.AutonatedReportsServiceTest.php -filter=testUpdateAskJiminnyReportStatus100% 2• Tue 19 May 15:19:14+0 ..+2-1Run#+ Skip0004 difterencesAPush reiectedorigin/JY-20676-delete-report-related-objects... vShow details in console124-6UTE.8Po 4 spaces...
|
58357
|
NULL
|
NULL
|
NULL
|
|
58362
|
2062
|
10
|
2026-05-19T12:19:15.975768+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193155975_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslremote:Counting objects: 100% (2/2), done.Compressing objects: 100% (2/2),done.remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7Merge made by the'ort'JY-20676-delete-report-related-objects ->origin/JY-20676-dele*strategy.routes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:100% <8•Tue 19 May 15:19:15Describe what you are looking for• •EDActivityAllHomeDMsActivityFilesLater..•More→DMs+C9 Unreads•Nikolay Yan.. Reacted2:13 PMDMКачих клипче тук - добре се държи...Jira Cloud2:12 PMAppNikolay Yankov commented on aStory you are assigned to...Galya Dimitrova12:50 PMDMедно е ок, няма нужда да ги делим.Той като влезе на едит ще си види ...Review Toast11:51 AM::: AppPR review requested by@steliyan-gVasil VasilevSO DMблагодаря10:29 AMSelect a notification to view the details.+YesterdayAneliya AngelovaReacted in DMYesterdayVAI ТЛОIО РА ПОПЛАЙЦЯ ТАРАОЯ...
|
NULL
|
-6067772165664405842
|
NULL
|
visual_change
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslremote:Counting objects: 100% (2/2), done.Compressing objects: 100% (2/2),done.remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7Merge made by the'ort'JY-20676-delete-report-related-objects ->origin/JY-20676-dele*strategy.routes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:100% <8•Tue 19 May 15:19:15Describe what you are looking for• •EDActivityAllHomeDMsActivityFilesLater..•More→DMs+C9 Unreads•Nikolay Yan.. Reacted2:13 PMDMКачих клипче тук - добре се държи...Jira Cloud2:12 PMAppNikolay Yankov commented on aStory you are assigned to...Galya Dimitrova12:50 PMDMедно е ок, няма нужда да ги делим.Той като влезе на едит ще си види ...Review Toast11:51 AM::: AppPR review requested by@steliyan-gVasil VasilevSO DMблагодаря10:29 AMSelect a notification to view the details.+YesterdayAneliya AngelovaReacted in DMYesterdayVAI ТЛОIО РА ПОПЛАЙЦЯ ТАРАОЯ...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58363
|
2063
|
6
|
2026-05-19T12:19:49.400632+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193189400_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5866593802844781938
|
-1967648623812184945
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58364
|
2062
|
11
|
2026-05-19T12:19:51.164216+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193191164_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1853586474279680935
|
-2255878992589005425
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections...
|
58362
|
NULL
|
NULL
|
NULL
|
|
58365
|
2063
|
7
|
2026-05-19T12:20:19.750059+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193219750_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8402057593159113418
|
-8745529780327979121
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
58363
|
NULL
|
NULL
|
NULL
|
|
58366
|
2062
|
12
|
2026-05-19T12:20:21.537333+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193221537_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1853586474279680935
|
-2255878992589005425
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58367
|
2063
|
8
|
2026-05-19T12:20:50.063544+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193250063_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"bounds":{"left":0.66921544,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.7017952,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7287234,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"bounds":{"left":0.42652926,"top":0.415004,"width":0.29321808,"height":0.013567438},"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8650878192689697423
|
-1412888448243747095
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58368
|
2062
|
13
|
2026-05-19T12:20:51.882161+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193251882_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8650878192689697423
|
-1412888448243747095
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
58366
|
NULL
|
NULL
|
NULL
|
|
58369
|
2063
|
9
|
2026-05-19T12:21:20.328828+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193280328_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8650878192689697423
|
-1412888448243747095
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
58367
|
NULL
|
NULL
|
NULL
|
|
58370
|
2062
|
14
|
2026-05-19T12:21:22.123229+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193282123_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8650878192689697423
|
-1412888448243747095
|
idle
|
accessibility
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58371
|
2063
|
10
|
2026-05-19T12:21:50.582509+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193310582_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"on_screen":true,"value":"Push rejected\nPush to origin/JY-20676-delete-report-related-objects was rejected","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.028922873,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.915403,"width":0.09740692,"height":0.040702313},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show details in console","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.047872342,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8529518275171885412
|
-8889822539531736113
|
idle
|
hybrid
|
NULL
|
Push rejected
Push to origin/JY-20676-delete-repor Push rejected
Push to origin/JY-20676-delete-report-related-objects was rejected
text/html
text/html
text/html
text/html
Show details in console
Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
PhostormFV faVsco.js~Project100% 5&• Tue 19 May 15:21:50U AutomatedReportsServiceTestvLocal ChangesVIewINavicarecodeLaravelKeractorWindowmelp%9 JY-20676-delete-report-related-objects ~® ActivityController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.phpphp api.phpAskAnythingController.php© AutomatedReportsServiceTest.php xc) TimePerioditeratorlest.phoc) UpdateCrmDataResolverlest.php© AskAnythingPromptDto.phpW Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php© AskAnythingPromptServiceTest.phpKioskclass AutomatedReportsServiceTest extends TestCase→ AutomatedReports© Activity TypeServiceTest.php(C) AskJiminnvReportActivitvServicet© AskJiminnyReportServiceTest.php#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array $expected): void{...}(C) AutomatedReportsCallbackServiceC AutomatedReportsServiceActivitie:public function testGetMediaTypeFieldDataWithoutReport(): voidf...,(C) AutomatedReportsServiceExtende© AutomatedReportsServiceFieldDat136 %;public function testGetMediaTypeFieldDataWithReport(): voidf...;© AutomatedReportsServiceFormatti© AutomatedReportsServiceGetActiv1 usage© AutomatedReportsServiceGetPlaytpublic static function transformMediaTypesDataProvider(: array{...}© AutomatedReportsServiceReportG© AutomatedReportsServiceTest.php#[DataProvider('hasCallTypeConferenceDataProvider')]public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....C AutomatedReportsServiceValidateC AutomatedReportsServiceValidatic(ei DociniontCorvicaToct nhn#DataProvider('hascalurvoelialerdataProviden'SheliE custom.logA console [STAGING]E laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyC) CoachinaFeedhackCoachl.Icerin.nhnTx: AutovPlavaroundCascadefajiminny021 A1 A18 V.2 Y6 ^© Search.phpA 14 A102 V 73 ^ v 208Fix AutomatedReportsTests \Unit\Services\Kiosk\AutomatedReports\AutomatedReportsServiceupcate aok fimimy report status doce not val faste ihon ti sabling -3 passed (12 assertions)+0 ..SELECT * FROM automated_reports where id = 67;SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 282211=212select * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;Inoughts6/6 tasks done ›Thoughts >Fixed. The issue was that AutomatedReportsService uses readonly properties (PHP 8.2+), so reflection-based propertymoditication fails. Uodated the aetService heloer to acceot a SmockReno parameter and iniect it durina constructioninstead. All 3 tests now pass.= 215214E217select * from activities where crm_configuration_id = 39and recordind state =recorded'and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01";=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused+ <Code SWE-1.6Changes 3 tilesE.env.local appTLO+→EDo not ignore856ca84faapp/Console/Commands/JiminnyDebugCommand.phpHighight words → [X 13 tesCurrent versionphe logging.php configv Unversioned Flles g tllesE .env.nikilocal app= env.other apo© CanAccessAiReportsTest.php tests/Unit/PoliciesC) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Rhravicon.ico public= ids.txt aodTaraw_sql_query.sql appC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/HubsnotMLWERHOOK_FILTERING_IMPLEMENTATION.md aodnamespace Jiminny\Console\Commands;namespace Jiminny\Console\Commands;use Illuminate\Console\Command;use Jiminny \Models\Team;use Illuminate\Console\Command;use Illuminate|Support\Facades\Redis;use Jiminny \Models\Team;* Class JiminnyDebugCommand* Class JiminnyDebugCommand* Cpackage Jiminny\Console\Commands* Cpackage Jiminny\Console\Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug';protected Ssiqnature = '11minnv:debua saction?'*public function handle(): voidpublic function handle(): voidsteam = Team::findOrFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->arqument('action'):var dumo(Susersids):die;exitanif Saction zz= 'redis-set') "return;O al ..Start recordina (24M)4 difterencesA Push rejectedorigin/JY-20676-delete-report-related-objects...~Show details in console134:6 UTF-8f?4 spaces...
|
58367
|
NULL
|
NULL
|
NULL
|
|
58372
|
2062
|
15
|
2026-05-19T12:21:52.367833+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193312367_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslremote:Counting objects: 100% (2/2), done.Compressing objects: 100% (2/2),done.remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7Merge made by the'ort'JY-20676-delete-report-related-objects ->origin/JY-20676-dele*strategy.routes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:100% <8•Tue 19 May 15:21:52Describe what you are looking for• •EDActivityAllHomeDMsActivityFilesLater..•More→DMs+C9 Unreads•Nikolay Yan.. Reacted2:13 PMDMКачих клипче тук - добре се държи...Jira Cloud2:12 PMAppNikolay Yankov commented on aStory you are assigned to...Galya Dimitrova12:50 PMDMедно е ок, няма нужда да ги делим.Той като влезе на едит ще си види ...Review Toast11:51 AM::: AppPR review requested by@steliyan-gVasil VasilevSO DMблагодаря10:29 AMSelect a notification to view the details.+YesterdayAneliya AngelovaReacted in DMYesterdayVAI ТЛОIО РА ПОПЛАЙЦЯ ТАРАОЯ...
|
NULL
|
146627237264259254
|
NULL
|
idle
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslremote:Counting objects: 100% (2/2), done.Compressing objects: 100% (2/2),done.remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7Merge made by the'ort'JY-20676-delete-report-related-objects ->origin/JY-20676-dele*strategy.routes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-ob:100% <8•Tue 19 May 15:21:52Describe what you are looking for• •EDActivityAllHomeDMsActivityFilesLater..•More→DMs+C9 Unreads•Nikolay Yan.. Reacted2:13 PMDMКачих клипче тук - добре се държи...Jira Cloud2:12 PMAppNikolay Yankov commented on aStory you are assigned to...Galya Dimitrova12:50 PMDMедно е ок, няма нужда да ги делим.Той като влезе на едит ще си види ...Review Toast11:51 AM::: AppPR review requested by@steliyan-gVasil VasilevSO DMблагодаря10:29 AMSelect a notification to view the details.+YesterdayAneliya AngelovaReacted in DMYesterdayVAI ТЛОIО РА ПОПЛАЙЦЯ ТАРАОЯ...
|
58370
|
NULL
|
NULL
|
NULL
|
|
58394
|
2065
|
9
|
2026-05-19T12:23:53.846792+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193433846_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormFV faVsco.js~roledeyViewINavicarecodeLarav PhostormFV faVsco.js~roledeyViewINavicarecodeLaravelKeractorWindowmelp%9 JY-20676-delete-report-related-objects ~...:-® ActivityController.phpAskAnythingController.phpE custom.logE laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php xA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php© AskAnythingPromptDto.phpTx: AutovPlavaroundfajiminny› Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php© AskAnythingPromptServiceTest.php© Search.php021 A1 A18 V.2 Y6 ^~ D Kioskclass AutomatedReportsServiceTest extends TestCaseA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67;v _ Automated ReportsSELECT * FROM automated_reports where id = 42;© Activity TypeServiceTest.phpSELECT * FROM users WHERE id = 143; # group 28(C) AskJiminnvReoortActivitvServiceTel#[DataProvider('transformMediaTypesDataProvider')]© AskJiminnyReportServiceTest.php2211114 vpublic function testTransformMediaTypes(array $mediaTypes, array $expected): voidf..,=212(C) AutomatedReportsCallbackServicelC AutomatedReportsServiceActivitiespublic function testGetMediaTypeFieldDataWithoutReport(): voidf...}(C) AutomatedReportsServiceExtendedAutomatedReportsServiceFieldDataselect * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;136 %;public function testGetMediaTypeFieldDataWithReport(): voidf...;© AutomatedReportsServiceFormattir© AutomatedReportsServiceGetActivi1 usage= 215214E217© AutomatedReportsServiceGetPlaybpublic static function transformMediaTypesDataProvider(): arrayf..,select * from activities where crm_configuration_id = 39and recordind state &and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';© AutomatedReportsServiceReportGeAutomatedReportsServiceTest.php© AutomatedReportsServiceValidateF190 V.#[DataProvider('hasCallTypeConferenceDataProvider')]public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;© AutomatedReportsServiceValidatiorSocket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused100% 5&• Tue 19 May 15:23:53U AutomatedReportsServiceTestvFix AutomatedReportsTests \Unit\Services\Kiosk\AutomatedReports\AutomatedReportsServiceupete ak. jiary report status does nor val fate vhen dt attange3 passed (12 assertions)+0 ..Inoughts6/6 tasks done ›Thoughts >Fixed. The issue was that AutomatedReportsService uses readonly properties (PHP 8.2+), so reflection-based propertymoditication fails. Uodated the aetService heloer to acceot a SmockReno parameter and iniect it durina constructioninstead. All 3 tests now pass.+ <Code SWE-1.6#DataProvider('hascalurvoelialerdataProviden'Local ChangesSheliChanges 3 tilesE.env.local appT.ODo not ignore856ca84faapp/Console/Commands/JiminnyDebugCommand.phpHighight words → [X 13 tesCurrent versionphe logging.php configv Unversioned Flles & tllegE .env.nikilocal app= env.other apo© CanAccessAiReportsTest.php tests/Unit/PoliciesC) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Rfavicon.ico public= ids.tyt andTaraw_sql_query.sql appC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/HubsnotMLWERHOOK_FILTERING_IMPLEMENTATION.md aodnamespace Jiminny\Console\Commands;namespace Jiminny\Console\Commands;use Illuminate\Console\Command;use Jiminny \Models\Team;use Illuminate\Console\Command;use Illuminate|Support\Facades\Redis;use Jiminny \Models\Team;* Class JiminnyDebugCommand* Class JiminnyDebugCommand* Cpackage Jiminny\Console\Commands* Cpackage Jiminny\Console\Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug';protected Ssiqnature = '11minnv:debua saction?'*public function handle(): voidpublic function handle(): voidsteam = Team::findOrFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@ •Saction = Sthis->arqument('action'):var dumo(Susersids):die;exitanif Saction zz= 'redis-set') <return;0 al ..4 difterencesA Push rejectedorigin/JY-20676-delete-report-related-objects...~Show details in console134:6 UTF-8f?4 spaces...
|
NULL
|
-4084897452581563832
|
NULL
|
app_switch
|
ocr
|
NULL
|
PhostormFV faVsco.js~roledeyViewINavicarecodeLarav PhostormFV faVsco.js~roledeyViewINavicarecodeLaravelKeractorWindowmelp%9 JY-20676-delete-report-related-objects ~...:-® ActivityController.phpAskAnythingController.phpE custom.logE laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyCascade© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php xA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhn© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php© AskAnythingPromptDto.phpTx: AutovPlavaroundfajiminny› Internal© AskJiminnyReportsController.phg© AutomatedReportsService.php© AskAnythingPromptServiceTest.php© Search.php021 A1 A18 V.2 Y6 ^~ D Kioskclass AutomatedReportsServiceTest extends TestCaseA14 A102 V73 ^ v 208SELECT * FROM automated_reports where id = 67;v _ Automated ReportsSELECT * FROM automated_reports where id = 42;© Activity TypeServiceTest.phpSELECT * FROM users WHERE id = 143; # group 28(C) AskJiminnvReoortActivitvServiceTel#[DataProvider('transformMediaTypesDataProvider')]© AskJiminnyReportServiceTest.php2211114 vpublic function testTransformMediaTypes(array $mediaTypes, array $expected): voidf..,=212(C) AutomatedReportsCallbackServicelC AutomatedReportsServiceActivitiespublic function testGetMediaTypeFieldDataWithoutReport(): voidf...}(C) AutomatedReportsServiceExtendedAutomatedReportsServiceFieldDataselect * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;136 %;public function testGetMediaTypeFieldDataWithReport(): voidf...;© AutomatedReportsServiceFormattir© AutomatedReportsServiceGetActivi1 usage= 215214E217© AutomatedReportsServiceGetPlaybpublic static function transformMediaTypesDataProvider(): arrayf..,select * from activities where crm_configuration_id = 39and recordind state &and duration > 60and status = 'completed" and actual_start_time >= '2025-12-01';© AutomatedReportsServiceReportGeAutomatedReportsServiceTest.php© AutomatedReportsServiceValidateF190 V.#[DataProvider('hasCallTypeConferenceDataProvider')]public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;© AutomatedReportsServiceValidatiorSocket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refused100% 5&• Tue 19 May 15:23:53U AutomatedReportsServiceTestvFix AutomatedReportsTests \Unit\Services\Kiosk\AutomatedReports\AutomatedReportsServiceupete ak. jiary report status does nor val fate vhen dt attange3 passed (12 assertions)+0 ..Inoughts6/6 tasks done ›Thoughts >Fixed. The issue was that AutomatedReportsService uses readonly properties (PHP 8.2+), so reflection-based propertymoditication fails. Uodated the aetService heloer to acceot a SmockReno parameter and iniect it durina constructioninstead. All 3 tests now pass.+ <Code SWE-1.6#DataProvider('hascalurvoelialerdataProviden'Local ChangesSheliChanges 3 tilesE.env.local appT.ODo not ignore856ca84faapp/Console/Commands/JiminnyDebugCommand.phpHighight words → [X 13 tesCurrent versionphe logging.php configv Unversioned Flles & tllegE .env.nikilocal app= env.other apo© CanAccessAiReportsTest.php tests/Unit/PoliciesC) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Rfavicon.ico public= ids.tyt andTaraw_sql_query.sql appC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/HubsnotMLWERHOOK_FILTERING_IMPLEMENTATION.md aodnamespace Jiminny\Console\Commands;namespace Jiminny\Console\Commands;use Illuminate\Console\Command;use Jiminny \Models\Team;use Illuminate\Console\Command;use Illuminate|Support\Facades\Redis;use Jiminny \Models\Team;* Class JiminnyDebugCommand* Class JiminnyDebugCommand* Cpackage Jiminny\Console\Commands* Cpackage Jiminny\Console\Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug';protected Ssiqnature = '11minnv:debua saction?'*public function handle(): voidpublic function handle(): voidsteam = Team::findOrFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@ •Saction = Sthis->arqument('action'):var dumo(Susersids):die;exitanif Saction zz= 'redis-set') <return;0 al ..4 difterencesA Push rejectedorigin/JY-20676-delete-report-related-objects...~Show details in console134:6 UTF-8f?4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58403
|
2064
|
13
|
2026-05-19T12:25:30.441509+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193530441_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-6966022010367698874
|
-564708849257816637
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of_jimi...• Direct messagesRio Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevE. Lukas Kovalik y...::: Apps|Jira CloudToastall100% <78• Tue 19 May 15:25:30Describe what you are looking forToastHomeMessagesAboutYesterdayToday ~Toast APP10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old - 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP11:51 AMPR review requested by @steliyan-g (edited)PR review requested by@Steliyan Georgiev#518 JY-20919: Lower execreports batch size by @SteliyanGeorgiev1 commit • 3 files changediiminny/prophetMessage Toast+...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58404
|
2065
|
14
|
2026-05-19T12:25:30.424677+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193530424_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5997195330951662087
|
588213168564012433
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
PhostormViewINavicarecodeLaravelKeractorWindowmelpFV faVsco.js°9 JY-20676-delete-report-related-objectsProleteyC ActivityController.ong© AskAnythingController.php=custom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euy© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© TimePerioditeratorTest.php© AskAnythingPromptDto.php© UpdateCrmDataResolverTest.php› Internalv C Kioskv _ Automated ReportsC ActivitvTvpeServiceTest.oho© AutomatedReportsServiceTest.php XA console [STAGING]C) CoachinaFeedhackCoachl.Icerin.nhnTx: AutovPlavaroundSo jiminny v© AskJiminnyReportsController.phg© AutomatedReportsService.php©) AskAnythingPromptServiceTest.php© Search.php021 A1 A18 V.2 Y6 ^class AutomatedReportsservicelest extends TestcaseA 14 A102 V 73 ^ v 208SELECT * FROM automated_reports where id = 67:SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28(C) AskJiminnvReoortActivitvServiceTel(C) AskJiminnvReoortServiceTest.ono114 v#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}2211—212(C) AutomatedReportsCallbackServicelpublic function testGetMediaTypeFieldDataWithoutReport: void{...}(C) AutomatedReportsServiceActivities(C) AutomatedReportsServiceExtendedselect * from teams where id = 3143:select * from crm_configurations where id = 500;select * from users where name = 'Integration Account': # 1695SELECT * FROM social accounts WHERE sociable id = 1695:136 %public function testGetMediaTypeFieldDataWithReport: void{...}© AutomatedReportsServiceFieldDate(c) AutomatedRenortsServiceSormattin(C) AutomatedRenortsServiceGetActivi1 usage1219214E217select * from activities where crm confiquration 1d = 39and recordind state &recorded'and duration > 60public static function transformMediaTypesDataProvider: array{...}© AutomatedReportsServiceGetPlayb©) AutomatedReportsServiceReportGe©) AutomatedReportsServiceTest.php(eh AutamatodDonortcCorvicoValidatorand status = 'completed" and actual_start_time >= '2025-12-01';#Uararrovzdernascalu.voeconterencevararrovzder')=221SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;190 ₽public function testrascal tvoeconferencelarrav Scalutvoes. bool Sexpected): vord....(eh AutamatodDonorteCorvicoValidatio(ei DociniontCorvicaToct nhn#DataProvider('hascalutvoelialerDataProviden'Socket tail to connect to host.address=(host=localhost) nort=3306/tvoe=orimarv). Connection refusedLocal ChandesSheliChanges 3 tiles=.env.local appT.ODo not ignore8 56ca84fa app/Console/Commands/JiminnyDebugCommand.phpHighlight words 15Current versionc) JiminnyDebugCommand.php app/Console/ephp logging.php contigv Unversioned Flles & tllegE .env.nikilocal app= env.other apoC CanAccessAiReportsTest.php tests/Unit/Policiesnamecnace liminnv Concolle Commands.namecnace liminnv Concolle Commands.use Illuminate\Console\Commanduse Jiminny Models\Team;use Tlluminatel Concole Command•C) CreateMockAskJiminnvReoortResultCommand,oho aoo/Console/Commands/Ruse Illuminate\Support\Facades \Redis:use liminnv Modelc Toam•k ravicon.ico public= ids.tyt andR raw sal querv.sal ann* Class JiminnyDebugCommano* Class JiminnyDebugCommandC) SimulateWebhooksCommand.nhn ann/Console/Commands/Crm/Hubsnot* @package Jiminnu\ Console\ CommandsMLWERHOOK_FILTERING_IMPLEMENTATION.md aod* @package Jiminnu\ Console\ Commandsclass JiminnyDebugCommand extends Commandclass JiminnyDebugCommand extends Commandprotected Ssignature = 'jiminny:debug':protected Ssiqnature = '11minny:debua saction?'*public function handled: voidnublic function handled): void$team = Team: :find0rFail(2);SusersIds = Steam->usens@->pluck('users.id')->toArrav@•Saction = Sthis->arqument('action'):var dumo(Susersids):die:exitanif Saction zz= 'redis-set') "return;100% 5• Tue 19 May 15:25:30AutomatedReportsServiceTest ~CascadeFix AutomatedReports+0 ..leststunitiservicesKiosk\automatedkeports\automatedkeportsserviceupdate ask jiminny report status throws when enabling with missin...3 passed (12 assertions)Inoughts6/6 tasks done ›Thoughts>Fixed. The issue was that AutomatedReportsService uses readonly properties (PHP 8.2+), so reflection-based propertymoditication fails. Uodated the aetService heloer to acceot a SmockReno parameter and iniect it durina constructioninstead. All 3 tests now pass©tl •Ask anvthina (*4D1¿ @sCodo CWS.1A4 difterencesW Windsurf Teams 134:6 UTF-8 P 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58405
|
2065
|
15
|
2026-05-19T12:25:46.406041+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193546406_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4474734,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.45611703,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.46476063,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.47573137,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4867021,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.51329786,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5242686,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.70611703,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"21","depth":4,"bounds":{"left":0.66921544,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.7017952,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7287234,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","depth":4,"on_screen":true,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM activities WHERE uuid_to_bin('e916569b-086c-4bd1-94d7-5e3802c27ccf') = uuid;\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role;\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT * FROM automated_reports where id = 71;\nSELECT * FROM automated_report_results where report_id = 71;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE 1=1\n AND `automated_report_results`.`generated_at` IS NOT NULL\n# AND `automated_report_results`.`sent_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 143, '$.\"users\"')\n;\n\nSELECT * FROM automated_reports where id = 67;\nSELECT * FROM automated_reports where id = 42;\nSELECT * FROM users WHERE id = 143; # group 28\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;\n\nSELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003\nSELECT * FROM activities WHERE id IN (16,422003);\nSELECT * FROM activities where status = 'failed';\n\nSELECT * FROM tracks WHERE activity_id = 422003;\n\nSELECT\n a.*\nFROM activities a\nJOIN users u ON a.user_id = u.id\nWHERE\n a.status = 'completed'\n AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuid\n AND a.deleted_at IS NULL\n AND EXISTS (\n SELECT 1 FROM tracks t\n WHERE t.activity_id = a.id\n AND t.type IN ('audio', 'video')\n )\nORDER BY a.actual_start_time DESC\nLIMIT 25;\n\nselect * from teams where id = 19;\nselect * from crm_configurations where provider = 'pipedrive';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 19 and sa.provider = 'pipedrive';\n\nSELECT * FROM social_accounts WHERE id = 1116;\n\nUPDATE social_accounts SET provider_user_token = 'v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA',\nprovider_refresh_token = '5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc',\nexpires = 1779091997,\nstate = 'connected'\nWHERE id = 1116;\n\n \"provider_user_token\": \"v1u:AQIBAHj-LzTNK2yuuuaLqifzhWb9crUNKTpk4FlQ9rjnXqp_6AEQhDhDQVa1nvWCHEvnpvSEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnG8KNcZLIjEnlRPxAgEQgDsGPIcKjsMU7Qel36BtM5FCQa56mYUy24_AAoqd12yjVFkq6egLqS0inp-5G4JE7frJURMV8VTw_fY14g:rYyABXmXsBhEdYU_dfmDH8GF-vzSseJXE5bds_zAyVdAwlTXOPdTl9i4PS4jVofLvpq7IRgvEt2BzGhR6cWiQXHHD0AtayQOkZm262ClFCsMYtKGfep0Jq1n0eiRIVqT9gAY7rWvhpEDF8oAlgnRGmqx-euIkpzE79sPXh4OjNx_yUIaanjyplGpBBJ_NiACd1sMlZmseyUyyU_gldqlCBAuK9hhATc9icgg7zuoc7nKBBrlg49tRtzEagDh6xbFBpzfCp7kE_7n4TLWfWHLPMzu-bktjwA969G9sFRmoH1GjPA0a2odDT4dk1_1ouBrB1NcTT2hQUqH-_RzDW2nWmeoVHA\",\n \"provider_refresh_token\": \"5034113:19555731:87c14258f0c813d02767ee975f6044d46b6b2bfc\",\n \"expires\": 1779091997,","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","depth":3,"bounds":{"left":0.42652926,"top":0.9577015,"width":0.29321808,"height":0.013567438},"on_screen":true,"value":"Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Connection refused","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
58404
|
NULL
|
NULL
|
NULL
|
|
58406
|
2064
|
14
|
2026-05-19T12:25:54.632412+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193554632_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1961873295031593158
|
-1483003541378331465
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...• Direct messagesRio Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevE. Lukas Kovalik y...::: Apps|Jira CloudToastall100% <78• Tue 19 May 15:25:54Describe what you are looking forToastHomeMessagesAboutYesterdayToday ~Toast APP10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old - 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP11:51 AMPR review requested by @steliyan-g (edited)PR review requested by@Steliyan Georgiev#518 JY-20919: Lower execreports batch size by @SteliyanGeorgiev1 commit • 3 files changediiminny/prophetMessage Toast+...
|
58403
|
NULL
|
NULL
|
NULL
|
|
58407
|
2065
|
16
|
2026-05-19T12:25:54.585623+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193554585_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5190014106386194985
|
588143888594044309
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
PhostormFV faVsco.js~ProjectINavicarecode%9 JY-20676-delete-report-related-objects® ActivityController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.phpAskAnythingController.php© AutomatedReportsServiceTest.php x© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php© AskAnythingPromptDto.php› Internal© AskJiminnyReportsController.phg© AskAnythingPromptServiceTest.php~ D Kioskclass AutomatedReportsServiceTest extends TestCase© Search.php207A14 A102 У73 ^ v 208v _ Automated Reports© Activity TypeServiceTest.php(C) AskJiminnvReoortActivitvServiceTel© AskJiminnyReportServiceTest.php114 %#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array $expected): voidf..,—210+211212(C) AutomatedReportsCallbackServicelC AutomatedReportsServiceActivities(C) AutomatedReportsServiceExtended© AutomatedReportsServiceFieldDate136 %;public function testGetMediaTypeFieldDataWithoutReport(): voidf...}public function testGetMediaTypeFieldDataWäthReport(): voidf...,© AutomatedReportsServiceFormattir© AutomatedReportsServiceGetActivi© AutomatedReportsServiceGetPlaybpublic static function transformMediaTypesDataProvider(): arrayf..,© AutomatedReportsServiceReportGe© AutomatedReportsServiceTest.php-218=219=220© AutomatedReportsServiceValidateF190 ₽#[DataProvider('hasCallTypeConferenceDataProvider')]public function testHasCallTypeConference(array $callTypes, bool Sexpected): voidf...}© AutomatedReportsServiceValidatior=222=223C RecipientServiceTest.php> D Mail201 8>#DataProvider('hascalutvoelialerDataProviden'public function testHasCallTypeDialer(array $callTypes, bool $expected): voidf...}> D MeetingGenerator> D Notification1 usageDRecallAInublic static function hasCallTvneConferenceDataProvider(): arnavf...}= 227= 228D Team_ lelephony/UserUserPillot• Webhook247 8©) ActivitvServiceTest.ohoC ApiResponse ServiceTest.php257 81 usagepublic static function hasCallTypeDialerDataProvider(): arrayf...public function testTransformReportResultsWithEmptyCollection(): voidt....public function testTransformReportResultsStructure(): voidk....=230231C InstantMeetinaServiceTest.pho= 233-234235C IntercomServiceTest.php297 86public function testTransformReportResultsWithMultipleResults(): voidf...}@ KioskServiceTest.ohn© PlanhatServiceTest.php@ PlavbackVideoOnlyServiceTest.php323 %© PlaybookCategoryServiceTest.phpC ResolveTeamCrmConnectionTest.php© SlackServiceTest.php342 %@ SocialAccountServiceTest.php© TeamDeactivatedServiceTest.php© TeamOwnerServiceTest.phpC TeamService Test.php© UserServiceTest.php> D Traits#[DataProvider('isUserRecipient0fReportDataProvider')]publ2o function testsUsenRecápientofReport(int Susentd, array Srecäpients, bool Sexpected) : vozdi 239#[DataProvider('isUserRecipientOfAskJimianxReRrtDataPrevider')]public function testIsUserRecipient0fAskJiminnyReportViaGroupCint suserid?int SaroupIdarray srecipients,arrav SreportGroups.243=245=246bool $expected,): void f...}> D UseCases> DUser> DUtils› D Validation› DVOE custom.logE laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyCascadeFix AutomatedReportsfajiminnym 021 A1 A18 V2 V6 ^ VCascade50 lal100% Lz&• Tue 19 May 15:25:54AutomatedReportsServiceTest+0 ..SELECT * FROM automated_reports where id = 42;SELECT * FROM users WHERE id = 143; # group 28select * from teams where id = 3143;select * from crm_configurations where id = 500;select * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;select * from activities where crm_configuration_id = 39and recordind state ='recorded' and duration > 60and status = 'completed' and actual_start_time >= ^2025-12-01';SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;select * from leads:SELECT * FROM activities WHEREuuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003SELECT * EROMid TM (16.422003) -SELECT * FROMactivitios whene status = Ifailed!.SELECT * FROM tracks WHERE activity_id = 422003;CEISATFROM activities aIOTM ncone n 1 nc-s1: 0M a ncon id em.ida.status = 'completed'AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = U.uuidAMn a dolotod o+ Te MullnAND EXISTS (SELECT 1 FROM tracks tAND t. type IN ('audio',LIMIT 25;SEIECTsa.*Cascade Code *•Kick off a new project. Make changesacross your entre codedaseFix AutomatedReportsServiceTestC SCIM Role Management Implementation© Salesforce Token FallbackThere is a ticket with description:« Code SWF-1.6WN Windsurf Teams124-6UTF.8f?4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58410
|
2064
|
16
|
2026-05-19T12:26:10.854355+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193570854_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of_jimi...• Direct messagesRio Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevE. Lukas Kovalik y...::: Apps|Jira CloudToastall100% <78• Tue 19 May 15:26:10Describe what you are looking forToastHomeMessagesAboutYesterdayToday ~Toast APP10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old - 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP11:51 AMPR review requested by @steliyan-g (edited)PR review requested by@Steliyan Georgiev#518 JY-20919: Lower execreports batch size by @SteliyanGeorgiev1 commit • 3 files changediiminny/prophetMessage Toast+...
|
NULL
|
8434644594739165731
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of_jimi...• Direct messagesRio Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan TanevE. Lukas Kovalik y...::: Apps|Jira CloudToastall100% <78• Tue 19 May 15:26:10Describe what you are looking forToastHomeMessagesAboutYesterdayToday ~Toast APP10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old - 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP11:51 AMPR review requested by @steliyan-g (edited)PR review requested by@Steliyan Georgiev#518 JY-20919: Lower execreports batch size by @SteliyanGeorgiev1 commit • 3 files changediiminny/prophetMessage Toast+...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58411
|
2065
|
18
|
2026-05-19T12:26:10.817018+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193570817_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5190014106386194985
|
588143888594044309
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
PhostormProiectINavicarecodeFV faVsco.js?9 JY-20676-delete-report-related-objectsC ActivityController.ongcustom.log= laravel.log4 SF jiminny@localhost] x4 HS_local [jiminny@localhost]& console [PROD]# console [euy© ResolveCompanyNameByEmailTraitTe© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php› Internalv C Kioskv _ Automated ReportsC ActivitvTvpeServiceTest.oho© AskAnythingPromptService.php© AutomatedReportsServiceTest.php X© AskAnythingPromptDto.php© AskJiminnyReportsController.phgclass AutomatedReportsservicelest extends Testcase(C) AskJiminnvReoortActivitvServiceTel(C) AskJiminnvReoortServiceTest.ono114 %(C) AutomatedReportsCallbackServicel(C) AutomatedReportsServiceActivities(C) AutomatedReportsServiceExtended136 %© AutomatedReportsServiceFieldDate(c) AutomatedRenortsServiceSormattin(C) AutomatedRenortsServiceGetActivi© AutomatedReportsServiceGetPlayb©) AutomatedReportsServiceReportGe© AutomatedReportsServiceTest.php(eh AutamatodDonortcCorvicoValidator190 ₽(eh AutamatodDonorteCorvicoValidatio© RecipientServiceTest.php201 >> Ca Mail> C MeetinqGenerator> 0 NotificationC7 RecallAI0 Team_ lelephonyaUserUserPillot1 Webhook©) ActivitvServiceTest.oho© ApiResponseServiceTest.phpC InstantMeetinaServiceTest.pho@* IntercomServiceTest.php@ KioskServiceTest.ohnC PlanhatServiceTest.nho@ PlavbackVideoOnlyServiceTest.php© PlaybookCategoryServiceTest.phpC ResolveTeamCrmConnectionTest.php© SlackServiceTest.phpM CocialA ccountCorviceTact nhn217 V.257 %297 %323P342 P© TeamDeactivatedServiceTest.php© TeamOwnerServiceTest.php© TeamServiceTest.php© UserServiceTest.php> Co Traits> C UseCases> C Usen> C Utils> D Validation©) AskAnythingPromptServiceTest.php© Search.phpA14 A102 У73 ^ v 208#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array Sexpected): void{...}— 210÷8. 221212public function testGetMediaTypeFieldDataWithoutReport: void{...}public function testGetMediaTypeFieldDataWithReport(): voidf...=— 218public static function transformMediaTypesDataProvider(: array{...}#Uararrovzdernascalu.voeconterencevararrovzder')public function testHasCallTypeConference(array $callTypes. bool Sexpected): void{...}#DataProvider('hascalutvoelialerDataProviden'public function testHasCallTypeDialer(array $callTypes. bool Sexpected): voidf...}= 220221=222_2231 usagenublic static function hasCallTvneConferenceDataProvider(): arnavf...}-221— 2291 usagepublic static function hasCallTypeDialerDataProvider(): arrayf...}=230231public function testTransformReportResultsWithEmptyCollection(): voidf...}public function testTransformReportResultsStructure(): voidf...235public function testTransformReportResultsWithMultipleResults: void{...}#[DataProvider('isUserRecipient0fReportDataProvider')]public function testIsUserRecipientOfReport(int SuserId, array Srecipients, bool Sexpected) : void/239#[DataProvider('isUserRecipient0fAskJiminnyReportDataProvider')]public function testIsUserRecipient0fAskJiminnyReportViaGroupCint suserid?int SaroupIdarray srecipients,arrav SreportGroups.bool Sexpected.• void {...}243=245=246CascadeFix AutomatedReportsTx: AutovSo jiminny vm 021 A1 A18 V2 V6 ^ V:ELECT * FROM users WHERE id = 143: # group 28ielect * from teams where id = 3143:ielect * from crm_confiqurations where id = 500:¡elect * from users where name = 'Integration Account': # 1695BELECT * FROM social accounts WHERE sociable id = 1695:ielect * from activities where crm_configuration id = 39ind recordina state = 'recorded' and duration > 60ind status = 'completed' and actual start time >= 12025-12-01'•relect * from leads:SELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003PELECT * EROM activities WHERE id TM (16.422003) •PFLECT * EROM activitios whene status = Ifailedi.activity id = 422003+PSISCTIOTM ncone m1 nc-s1. 0M auncon id =midJHEpSactatue - IeomnlotodiAND uuid to_ bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuidAMn a dolotod o+ Te MultlAND EXISTSSELECT 1 FROM tracks +AND +.tvpe IN ('audio', 'video')FLECCascade100% 5• Tue 19 May 15:26:10AutomatedReportsServiceTest v+0 ... Fix AutomatedReportsServiceTest- SelM Role ManageSalestorce Token FallbackProblem: Both US and EU environments were processing the same Gmail emails for text relay, causing:rtailed (sender mismatch error code 2200)successfully deliverednny.com and the sameEitare e sille baced oa con fia imians nocale taet bocthe cuftent environmenUpaated envoproduction-euELAY HOST trom tyt lminnv com to tot lminnv erent: SMS received notifications sent with reoiv-to @txt.liminnv.com → oniv US orocesses reoliesent and verify SMS reply emails use @txt.jiminny.eu domainsucceeded in USWN Windsurf Teams124-6UTF.8io 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58412
|
2065
|
19
|
2026-05-19T12:26:16.683648+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193576683_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormFV faVsco.js vProjectavigatecode%9 JY-2067 PhostormFV faVsco.js vProjectavigatecode%9 JY-20676-delete-report-related-objects® ActivityController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php x© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php© AskAnythingPromptDto.php› Internal© AskJiminnyReportsController.phg© AskAnythingPromptServiceTest.php~ D Kioskclass AutomatedReportsServiceTest extends TestCase© Search.phpA14 A102 У73 ^ v 208v _ Automated Reports© Activity TypeServiceTest.php(C) AskJiminnvReoortActivitvServiceTel© AskJiminnyReportServiceTest.php114 %#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array $expected): void{..;-210+211212(C) AutomatedReportsCallbackServicelC AutomatedReportsServiceActivities(C) AutomatedReportsServiceExtended© AutomatedReportsServiceFieldDate136 %;public function testGetMediaTypeFieldDataWithoutReport(): voidf...}public function testGetMediaTypeFieldDataWäthReport(): voidf...,© AutomatedReportsServiceFormattir© AutomatedReportsServiceGetActivi© AutomatedReportsServiceGetPlayb-218public static function transformMediaTypesDataProvider(: array{...}© AutomatedReportsServiceReportGe© AutomatedReportsServiceTest.php© AutomatedReportsServiceValidateF190 ₽#[DataProvider('hasCallTypeConferenceDataProvider')]public function testHasCallTypeConference(array $callTypes, bool Sexpected): voidf...,© AutomatedReportsServiceValidatior=220221=222=223C RecipientServiceTest.php> D Mail201 8 >#DataProvider('hascalutvoelialerDataProviden'public function testHasCallTypeDialer(array $callTypes, bool $expected): voidf...}> D MeetingGenerator> D Notification1 usageDRecallAInublic static function hasCallTvneConferenceDataProvider(): arnavf...}= 227= 228D Team_ lelephonyaUserUserPillot• Webhook247 8©) ActivitvServiceTest.ohoC ApiResponse ServiceTest.php257 81 usagepublic static function hasCallTypeDialerDataProvider(): arrayf...public function testTransformReportResultsWithEmptyCollection(): voidt....public function testTransformReportResultsStructure(): voidk....=230231C InstantMeetinaServiceTest.pho= 233-234235C IntercomServiceTest.php297 86public function testTransformReportResultsWithMultipleResults(): voidf...}@ KioskServiceTest.ohn© PlanhatServiceTest.php@ PlavbackVideoOnlyServiceTest.php323 %© PlaybookCategoryServiceTest.phpC ResolveTeamCrmConnectionTest.php© SlackServiceTest.php342 %@ SocialAccountServiceTest.php© TeamDeactivatedServiceTest.php© TeamOwnerServiceTest.phpC TeamService Test.php© UserServiceTest.php> D Traits#[DataProvider('isUserRecipient0fReportDataProvider')]publ2o function testsUsenRecápientofReport(int Susentd, array Srecäpients, bool Sexpected) : vozdi 239#[DataProvider('isUserRecipientOfAskJimianxReRrtDataPrevider')]public function testIsUserRecipient0fAskJiminnyReportViaGroup(int suserid?int SaroupIdarray srecipients,arrav SreportGroups.243=245=246bool $expected,): void f...}> D UseCases> DUser> DUtils› D Validation› DVOE custom.logE laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyTx: Autovfajiminnym 021 A1 A18 V2 V6 ^ VSELECT * FROM users WHERE id = 143; # group 28ielect * from teams where id = 3143;ielect * from crm_configurations where id = 500;ielect * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;ielect * from activities where crm_configuration_id = 39ind recordina state = 'recorded' and duration > 60ind status = 'completed' and actual_start_time >= ^2025-12-01';relect * from leads:SELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003PELECT * EROM activities WHERE id TM (16.422003) •PFLECT * EROM activitios whene status = Ifailedi.activity_id = 422003;PSISCTIOIN users u 1..n<->1: ON a.user_id = u.idTHEREa.status = 'completed'AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuidAND a.deleted_at IS NULLAND EXISTS (SELECT 1 FROM tracks tAND t.type IN ('audio', 'video')CascadeFix AutomatedReportsCascadeQ Search© Implement Trial Owner Role Selection© Migrating to Pipedrive SDK v2© Text Relay Duplication Fix© Review PR for Activity StageManual Testing HubSoot PaginationiCascade100% Lz8• Tue 19 May 15:26:16AutomatedReportsServiceTest+0 ..current workspace800WCascade CodexKick off a new project. Make changesacross your entire codedaseFLECFix AutomatedReportsServiceTestC SCIM Role Management Implementation© Salesforce Token FallbackAsk anvthina (884-L)WN Windsurf Toams 124-6 UTF.8f?4 spaces...
|
NULL
|
-1942038485806315059
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormFV faVsco.js vProjectavigatecode%9 JY-2067 PhostormFV faVsco.js vProjectavigatecode%9 JY-20676-delete-report-related-objects® ActivityController.php© ResolveCompanyNameByEmailTraitTe© AskAnythingPromptService.php© AutomatedReportsServiceTest.php x© TimePerioditeratorTest.php© UpdateCrmDataResolverTest.php© AskAnythingPromptDto.php› Internal© AskJiminnyReportsController.phg© AskAnythingPromptServiceTest.php~ D Kioskclass AutomatedReportsServiceTest extends TestCase© Search.phpA14 A102 У73 ^ v 208v _ Automated Reports© Activity TypeServiceTest.php(C) AskJiminnvReoortActivitvServiceTel© AskJiminnyReportServiceTest.php114 %#[DataProvider('transformMediaTypesDataProvider')]public function testTransformMediaTypes(array $mediaTypes, array $expected): void{..;-210+211212(C) AutomatedReportsCallbackServicelC AutomatedReportsServiceActivities(C) AutomatedReportsServiceExtended© AutomatedReportsServiceFieldDate136 %;public function testGetMediaTypeFieldDataWithoutReport(): voidf...}public function testGetMediaTypeFieldDataWäthReport(): voidf...,© AutomatedReportsServiceFormattir© AutomatedReportsServiceGetActivi© AutomatedReportsServiceGetPlayb-218public static function transformMediaTypesDataProvider(: array{...}© AutomatedReportsServiceReportGe© AutomatedReportsServiceTest.php© AutomatedReportsServiceValidateF190 ₽#[DataProvider('hasCallTypeConferenceDataProvider')]public function testHasCallTypeConference(array $callTypes, bool Sexpected): voidf...,© AutomatedReportsServiceValidatior=220221=222=223C RecipientServiceTest.php> D Mail201 8 >#DataProvider('hascalutvoelialerDataProviden'public function testHasCallTypeDialer(array $callTypes, bool $expected): voidf...}> D MeetingGenerator> D Notification1 usageDRecallAInublic static function hasCallTvneConferenceDataProvider(): arnavf...}= 227= 228D Team_ lelephonyaUserUserPillot• Webhook247 8©) ActivitvServiceTest.ohoC ApiResponse ServiceTest.php257 81 usagepublic static function hasCallTypeDialerDataProvider(): arrayf...public function testTransformReportResultsWithEmptyCollection(): voidt....public function testTransformReportResultsStructure(): voidk....=230231C InstantMeetinaServiceTest.pho= 233-234235C IntercomServiceTest.php297 86public function testTransformReportResultsWithMultipleResults(): voidf...}@ KioskServiceTest.ohn© PlanhatServiceTest.php@ PlavbackVideoOnlyServiceTest.php323 %© PlaybookCategoryServiceTest.phpC ResolveTeamCrmConnectionTest.php© SlackServiceTest.php342 %@ SocialAccountServiceTest.php© TeamDeactivatedServiceTest.php© TeamOwnerServiceTest.phpC TeamService Test.php© UserServiceTest.php> D Traits#[DataProvider('isUserRecipient0fReportDataProvider')]publ2o function testsUsenRecápientofReport(int Susentd, array Srecäpients, bool Sexpected) : vozdi 239#[DataProvider('isUserRecipientOfAskJimianxReRrtDataPrevider')]public function testIsUserRecipient0fAskJiminnyReportViaGroup(int suserid?int SaroupIdarray srecipients,arrav SreportGroups.243=245=246bool $expected,): void f...}> D UseCases> DUser> DUtils› D Validation› DVOE custom.logE laravel.log4 SF jiminny@localhost] xA HS_Jocal [jiminny@localhost]A console [PROD]# console [euyTx: Autovfajiminnym 021 A1 A18 V2 V6 ^ VSELECT * FROM users WHERE id = 143; # group 28ielect * from teams where id = 3143;ielect * from crm_configurations where id = 500;ielect * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;ielect * from activities where crm_configuration_id = 39ind recordina state = 'recorded' and duration > 60ind status = 'completed' and actual_start_time >= ^2025-12-01';relect * from leads:SELECT * FROM activities WHERE uuid_to_bin('f43cf158-e60d-46e5-92f8-c4e0594a3219') = uuid; # 422003PELECT * EROM activities WHERE id TM (16.422003) •PFLECT * EROM activitios whene status = Ifailedi.activity_id = 422003;PSISCTIOIN users u 1..n<->1: ON a.user_id = u.idTHEREa.status = 'completed'AND uuid_to_bin('641f1acb-16b8-42d1-8726-df52979dad0e') = u.uuidAND a.deleted_at IS NULLAND EXISTS (SELECT 1 FROM tracks tAND t.type IN ('audio', 'video')CascadeFix AutomatedReportsCascadeQ Search© Implement Trial Owner Role Selection© Migrating to Pipedrive SDK v2© Text Relay Duplication Fix© Review PR for Activity StageManual Testing HubSoot PaginationiCascade100% Lz8• Tue 19 May 15:26:16AutomatedReportsServiceTest+0 ..current workspace800WCascade CodexKick off a new project. Make changesacross your entire codedaseFLECFix AutomatedReportsServiceTestC SCIM Role Management Implementation© Salesforce Token FallbackAsk anvthina (884-L)WN Windsurf Toams 124-6 UTF.8f?4 spaces...
|
58411
|
NULL
|
NULL
|
NULL
|
|
58413
|
2064
|
17
|
2026-05-19T12:26:18.395774+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193578395_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...• Direct messagesRo Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan Tanev. Lukas Kovalik y...::: Apps|Jira CloudToast> 0.all100% <78• Tue 19 May 15:26:17Describe what you are looking forToastHomeMessagesAbout@Vasil VasilevYesterday~approved by youToday ~Toast APP 10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old • 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP 11:51 AM#518 JY-20919: Lower exee reports batch size by@Steliyan Georgievmerged by @Steliyan Georgiev (author) after approval bynikolaybiaivanovMessage Toast+Aa...
|
NULL
|
399318578570262171
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1 SlackFileEditViewGoHistoryWindowHelp•APPDOCKER• ₴1DEV (docker)₴82APP (-zslCounting objects: 100% (2/2), done.Compressing objects: 100% (2/2),remote: Total 2 (delta 0),reused 0 (delta 0), pack-reusedUnpacking objects: 100% (2/2), 2.59 KiB | 1.29 MiB/s, done.From github.com:jiminny/app478e5b33d8..6ecd5a5ff7JY-20676-delete-report-related-objects ->origin/JY-20676-dele*Merge made by theroutes/api.php 17++++---1 file changed, 4 insertions(+),3 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obOn branch JY-20676-delete-report-related-objectsYour branch isahead of 'origin/JY-20676-delete-report-related-objects' by 2 commits.Cuse"git push"to publish your local commits)Changes notstaged for commit:(use"git add<file>..."to update what will becommitted)Cuse "git restore ‹file›..."to discard changesin working directory)modified:env.localmodified:app/Console/Commands/JiminnyDebugCommand.phpmodified:config/logging.phpUntracked files:Cuse "git add<file>..." to include in what will be committed)env.nikilocal.env.otherWEBHOOK_FILTERING_IMPLEMENTATION.mdapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.phpapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.phpids.txtpublic/favicon.icoraw_sql_query.sqltests/Unit/Policies/CanAccessAiReportsTest.phpnochanges added to commit (use "gitadd" and/or "git commit -a")lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obEnumerating objects: 24, done.Counting objects: 100% (18/18), done.Deltacompression using up to 8 threadsCompressing objects: 100% (9/9), done.Totin1o baects: 7, % s/10 ((e.e1 K), pack-rause/s, done.remote: Resolving deltas: 100% (7/7), completed with 6 local objects.To github.com:jiminny/app.git6ecd5a5ff7..56ca84fabdJY-20676-delete-report-related-objects -> JY-20676-delete-reporlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20676-delete-report-related-obHomeDMsActivityFilesLater..•MoreED→Jiminny ...# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...• Direct messagesRo Nikolay YankovD. Galya Dimitrova Me. Vasil Vasilev®. Aneliya Angelova8. Stefka Stoyanovao Stoyan TomovBs Todor Stamatov *Mario Georgiev. Nikolay Ivanovdo James GrahamStoyan Tanev. Lukas Kovalik y...::: Apps|Jira CloudToast> 0.all100% <78• Tue 19 May 15:26:17Describe what you are looking forToastHomeMessagesAbout@Vasil VasilevYesterday~approved by youToday ~Toast APP 10:00 AMMergeapp#12092 Allow owner's role to beselected when setting up a trial19 hours old • 6 files changedAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubReview Toast APP 11:51 AM#518 JY-20919: Lower exee reports batch size by@Steliyan Georgievmerged by @Steliyan Georgiev (author) after approval bynikolaybiaivanovMessage Toast+Aa...
|
58410
|
NULL
|
NULL
|
NULL
|
|
58414
|
2065
|
20
|
2026-05-19T12:26:22.968411+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193582968_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.098071806,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.37466756,"top":0.15003991,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"bounds":{"left":0.3863032,"top":0.15003991,"width":0.011968086,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"bounds":{"left":0.40026596,"top":0.15003991,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.14844373,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4192154,"top":0.14844373,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.42785904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.43650267,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
58415
|
2064
|
18
|
2026-05-19T12:26:24.818922+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779193584818_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20676-delete-report-related-objects, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20676-delete-report-related-objects","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"102","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"73","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Partner;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n $mockRepo = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n ($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeams(partner: $partner);\n }\n\n public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getOrganizationFieldData(partner: $partner);\n }\n\n public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void\n {\n $partner = $this->createMock(Partner::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects(self::once())\n ->method('getTeamsForKiosk')\n ->with(Team::STATUS_ACTIVE, $partner)\n ->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $service->getTeamsGroupsOptions(partner: $partner);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testUpdateAskJiminnyReportStatusThrowsWhenEnablingWithMissingReferences(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())\n ->method('isAskJiminnyReport')\n ->willReturn(true);\n $mockReport->expects($this->once())\n ->method('canExecute')\n ->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->never())->method('update');\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('This report is missing a saved search or prompt.');\n\n $service->updateAskJiminnyReportStatus($mockReport, true);\n }\n\n public function testUpdateAskJiminnyReportStatusDoesNotValidateWhenDisabling(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->never())->method('isAskJiminnyReport');\n $mockReport->expects($this->never())->method('canExecute');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('update')\n ->with($mockReport, ['status' => false])\n ->willThrowException(new \\RuntimeException('stop-after-update'));\n\n $service = $this->getService(mockRepo: $mockRepo);\n\n $this->expectException(\\RuntimeException::class);\n $this->expectExceptionMessage('stop-after-update');\n\n $service->updateAskJiminnyReportStatus($mockReport, false);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n\n public function testGetReportThrowsWhenNotFound(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('missing-uuid')\n ->willReturn(null);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('missing-uuid');\n }\n\n public function testGetReportWithDefaultPartnerSkipsPartnerCheck(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $report = new AutomatedReport();\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(true);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMatchingPartnerIdReturnsReport(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $partnerId = 5;\n $team = new Team();\n $team->partner_id = $partnerId;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn($partnerId);\n\n $result = $this->service->getReport('some-uuid', $partner);\n $this->assertSame($report, $result);\n }\n\n public function testGetReportWithNonDefaultPartnerAndMismatchedPartnerIdThrows(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $property = $reflection->getProperty('automatedReportsRepository');\n $property->setAccessible(true);\n /** @var AutomatedReportsRepository&\\PHPUnit\\Framework\\MockObject\\MockObject $mockRepo */\n $mockRepo = $property->getValue($this->service);\n\n $team = new Team();\n $team->partner_id = 5;\n\n $report = new AutomatedReport();\n $report->setRelation('team', $team);\n\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->willReturn($report);\n\n $partner = $this->createMock(Partner::class);\n $partner->method('isDefaultPartner')->willReturn(false);\n $partner->method('getId')->willReturn(2);\n\n $this->expectException(ModelNotFoundException::class);\n $this->service->getReport('some-uuid', $partner);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7285657229550227811
|
-1412888448243747103
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20676-delete-report-re Project: faVsco.js, menu
JY-20676-delete-report-related-objects, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
14
102
73
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Partner;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
$mockRepo = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
($mockRepo ?? $this->createMock(AutomatedReportsRepository::class)),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeams(partner: $partner);
}
public function testGetOrganizationFieldDataForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getOrganizationFieldData(partner: $partner);
}
public function testGetTeamsGroupsOptionsForwardsPartnerToRepository(): void
{
$partner = $this->createMock(Partner::class);
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->expects(self::once())
->method('getTeamsForKiosk')
->with(Team::STATUS_ACTIVE, $partner)
->willReturn(new Collection([]));
$service = $this->getService(mockTeamRepository: $mockTeamRepository);
$service->getTeamsGroupsOptions(partner: $partner);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - ...
|
NULL
|
NULL
|
NULL
|
NULL
|