From c814c265ea2e0ae1df89db2b0d49dea0da17aec8 Mon Sep 17 00:00:00 2001 From: kyle Date: Mon, 15 Jun 2026 12:38:38 -0700 Subject: [PATCH] chore: remove tests, composer, CI, and references to internal infrastructure for privacy reasons --- .gitea/workflows/ci.yml | 39 -- composer/autoload.php | 22 - composer/autoload_psr4.php | 10 - phpunit.xml.dist | 22 - tests/DebugToggleTest.php | 165 ------ tests/ImageCleanupTest.php | 212 ------- tests/NavigationProviderTest.php | 114 ---- tests/README.md | 118 ---- tests/TalkServiceTest.php | 987 ------------------------------- tests/WebhookControllerTest.php | 613 ------------------- tests/bootstrap.php | 137 ----- 11 files changed, 2439 deletions(-) delete mode 100644 .gitea/workflows/ci.yml delete mode 100644 composer/autoload.php delete mode 100644 composer/autoload_psr4.php delete mode 100644 phpunit.xml.dist delete mode 100644 tests/DebugToggleTest.php delete mode 100644 tests/ImageCleanupTest.php delete mode 100644 tests/NavigationProviderTest.php delete mode 100644 tests/README.md delete mode 100644 tests/TalkServiceTest.php delete mode 100644 tests/WebhookControllerTest.php delete mode 100644 tests/bootstrap.php diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml deleted file mode 100644 index bdac7ae..0000000 --- a/.gitea/workflows/ci.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: CI - -# TODO: Check if we can remove composer.json — CI currently uses `composer install` -# and `composer test` (wrappers around `phpunit` and `php -l`). If CI can use -# direct commands, composer.json can be dropped (no runtime deps needed). - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - -jobs: - php-tests: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - extensions: json, xml, mbstring - coverage: none - - - name: Install dependencies - working-directory: ./nc_bot_webhooks - run: composer install - - - name: Lint PHP files - working-directory: ./nc_bot_webhooks - run: composer lint - - - name: Run tests - working-directory: ./nc_bot_webhooks - run: composer test diff --git a/composer/autoload.php b/composer/autoload.php deleted file mode 100644 index f772b8a..0000000 --- a/composer/autoload.php +++ /dev/null @@ -1,22 +0,0 @@ - [$baseDir . '/lib'], -]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 2eb3efb..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - tests - - - - - - lib - - - diff --git a/tests/DebugToggleTest.php b/tests/DebugToggleTest.php deleted file mode 100644 index 6685955..0000000 --- a/tests/DebugToggleTest.php +++ /dev/null @@ -1,165 +0,0 @@ -createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(false); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput(['--status' => true]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('DISABLED', $output->fetch()); - } - - public function testStatusReturnsEnabledWhenSet(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(true); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput(['--status' => true]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('ENABLED', $output->fetch()); - } - - // ========================================================================= - // --enable - // ========================================================================= - - public function testEnableSetsValueToTrue(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(false); - $appConfig->expects($this->once()) - ->method('setValueBool') - ->with(self::APP_ID, self::DEBUG_KEY, true); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput(['--enable' => true]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('ENABLED', $output->fetch()); - } - - // ========================================================================= - // --disable - // ========================================================================= - - public function testDisableSetsValueToFalse(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(true); - $appConfig->expects($this->once()) - ->method('setValueBool') - ->with(self::APP_ID, self::DEBUG_KEY, false); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput(['--disable' => true]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('DISABLED', $output->fetch()); - } - - // ========================================================================= - // Toggle (no flags) - // ========================================================================= - - public function testToggleEnablesWhenDisabled(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(false); - $appConfig->expects($this->once()) - ->method('setValueBool') - ->with(self::APP_ID, self::DEBUG_KEY, true); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput([]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('enabled', $output->fetch()); - } - - public function testToggleDisablesWhenEnabled(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(true); - $appConfig->expects($this->once()) - ->method('setValueBool') - ->with(self::APP_ID, self::DEBUG_KEY, false); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput([]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(0, $result); - $this->assertStringContainsString('disabled', $output->fetch()); - } - - // ========================================================================= - // Conflict: --enable + --disable - // ========================================================================= - - public function testEnableAndDisableTogetherReturnsInvalid(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(false); - // Should NOT be called when both flags are set - $appConfig->expects($this->never()) - ->method('setValueBool'); - - $command = $this->makeCommand($appConfig); - $input = new ArrayInput(['--enable' => true, '--disable' => true]); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - - $this->assertEquals(1, $result); // Command::INVALID - $this->assertStringContainsString('Cannot use both', $output->fetch()); - } -} diff --git a/tests/ImageCleanupTest.php b/tests/ImageCleanupTest.php deleted file mode 100644 index 5916151..0000000 --- a/tests/ImageCleanupTest.php +++ /dev/null @@ -1,212 +0,0 @@ -createMock(TalkService::class); - $userManager = $this->createMock(IUserManager::class); - $userManager->method('get') - ->willReturn(null); // no bot user - - $logger = $this->createMock(LoggerInterface::class); - // Should NOT log anything since we exit early - $logger->expects($this->never()) - ->method('info'); - - $rootFolder = $this->createMock(IRootFolder::class); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(); // should not throw - } - - public function testRunReturnsEarlyWhenNoImagesDirectory(): void { - $talkService = $this->createMock(TalkService::class); - $userManager = $this->createMock(IUserManager::class); - - $botUser = $this->createMock(\OCP\IUser::class); - $botUser->method('getUID') - ->willReturn('talk-bot'); - $userManager->method('get') - ->willReturn($botUser); - - $rootFolder = $this->createMock(IRootFolder::class); - $rootFolder->method('getUserFolder') - ->willReturnCallback(function () { - $folder = $this->createMock(\OCP\Files\Folder::class); - $folder->expects($this->once()) - ->method('get') - ->willThrowException(new NotFoundException()); - return $folder; - }); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->never()) - ->method('info'); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(); // should not throw - } - - public function testRunReturnsEarlyOnRootFolderException(): void { - $talkService = $this->createMock(TalkService::class); - $userManager = $this->createMock(IUserManager::class); - - $botUser = $this->createMock(\OCP\IUser::class); - $botUser->method('getUID') - ->willReturn('talk-bot'); - $userManager->method('get') - ->willReturn($botUser); - - $rootFolder = $this->createMock(IRootFolder::class); - $rootFolder->method('getUserFolder') - ->willThrowException(new \Exception('filesystem error')); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->never()) - ->method('info'); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(); // should not throw - } - - // ========================================================================= - // Success paths - // ========================================================================= - - public function testRunPurgesOldImagesAndLogsCount(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('purgeOldImages') - ->willReturn(5); // purged 5 files - - $userManager = $this->createMock(IUserManager::class); - - $botUser = $this->createMock(\OCP\IUser::class); - $botUser->method('getUID') - ->willReturn('talk-bot'); - $userManager->method('get') - ->willReturn($botUser); - - $rootFolder = $this->createMock(IRootFolder::class); - $imagesFolder = $this->createMock(\OCP\Files\Folder::class); - $userFolder = $this->createMock(\OCP\Files\Folder::class); - - $userFolder->method('get') - ->willReturn($imagesFolder); - - $rootFolder->method('getUserFolder') - ->willReturn($userFolder); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with( - 'nc_bot_webhooks: purged 5 old image files', - ['app' => 'nc_bot_webhooks'], - ); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(); - } - - public function testRunDoesNotLogWhenNoFilesPurged(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('purgeOldImages') - ->willReturn(0); // no files purged - - $userManager = $this->createMock(IUserManager::class); - - $botUser = $this->createMock(\OCP\IUser::class); - $botUser->method('getUID') - ->willReturn('talk-bot'); - $userManager->method('get') - ->willReturn($botUser); - - $rootFolder = $this->createMock(IRootFolder::class); - $imagesFolder = $this->createMock(\OCP\Files\Folder::class); - $userFolder = $this->createMock(\OCP\Files\Folder::class); - - $userFolder->method('get') - ->willReturn($imagesFolder); - - $rootFolder->method('getUserFolder') - ->willReturn($userFolder); - - $logger = $this->createMock(LoggerInterface::class); - // Should NOT log when count is 0 - $logger->expects($this->never()) - ->method('info'); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(); - } - - // ========================================================================= - // Edge cases - // ========================================================================= - - public function testRunWithNullArgument(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('purgeOldImages') - ->willReturn(3); - - $userManager = $this->createMock(IUserManager::class); - - $botUser = $this->createMock(\OCP\IUser::class); - $botUser->method('getUID') - ->willReturn('talk-bot'); - $userManager->method('get') - ->willReturn($botUser); - - $rootFolder = $this->createMock(IRootFolder::class); - $imagesFolder = $this->createMock(\OCP\Files\Folder::class); - $userFolder = $this->createMock(\OCP\Files\Folder::class); - - $userFolder->method('get') - ->willReturn($imagesFolder); - - $rootFolder->method('getUserFolder') - ->willReturn($userFolder); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info'); - - $cleanup = $this->makeImageCleanup($talkService, $rootFolder, $userManager, $logger); - $cleanup->run(null); // null argument should work the same as no argument - } -} diff --git a/tests/NavigationProviderTest.php b/tests/NavigationProviderTest.php deleted file mode 100644 index 9839759..0000000 --- a/tests/NavigationProviderTest.php +++ /dev/null @@ -1,114 +0,0 @@ -createMock(IURLGenerator::class); - $userSession = $this->createMock(IUserSession::class); - $userSession->method('getUser') - ->willReturn(null); - $config = $this->createMock(IConfig::class); - $l10n = $this->createMock(IL10N::class); - - $provider = $this->makeNavigationProvider($urlGenerator, $userSession, $config, $l10n); - $nav = $provider->getNavigation(); - - $this->assertEquals([], $nav); - } - - public function testGetNavigationReturnsEmptyForNonAdminUser(): void { - $urlGenerator = $this->createMock(IURLGenerator::class); - $userSession = $this->createMock(IUserSession::class); - - $user = $this->createMock(IUser::class); - $user->method('isAdmin') - ->willReturn(false); - $userSession->method('getUser') - ->willReturn($user); - - $config = $this->createMock(IConfig::class); - $l10n = $this->createMock(IL10N::class); - - $provider = $this->makeNavigationProvider($urlGenerator, $userSession, $config, $l10n); - $nav = $provider->getNavigation(); - - $this->assertEquals([], $nav); - } - - // ========================================================================= - // Admin path - // ========================================================================= - - public function testGetNavigationReturnsLinkForAdminUser(): void { - $urlGenerator = $this->createMock(IURLGenerator::class); - $urlGenerator->method('linkToRoute') - ->willReturn('https://example.com/settings/admin'); - $urlGenerator->method('imagePath') - ->willReturn('https://example.com/apps/nc_bot_webhooks/app.svg'); - - $userSession = $this->createMock(IUserSession::class); - - $user = $this->createMock(IUser::class); - $user->method('isAdmin') - ->willReturn(true); - $userSession->method('getUser') - ->willReturn($user); - - $config = $this->createMock(IConfig::class); - - $l10n = $this->createMock(IL10N::class); - $l10n->method('t') - ->willReturnCallback(function ($text) { - return $text; - }); - - $provider = $this->makeNavigationProvider($urlGenerator, $userSession, $config, $l10n); - $nav = $provider->getNavigation(); - - $this->assertNotEmpty($nav); - $this->assertCount(1, $nav); - - $item = $nav[0]; - $this->assertEquals('nc_bot_webhooks', $item['id']); - $this->assertEquals('nc_bot_webhooks', $item['app_id']); - $this->assertEquals('settings', $item['type']); - $this->assertEquals('https://example.com/settings/admin', $item['href']); - $this->assertEquals('https://example.com/apps/nc_bot_webhooks/app.svg', $item['icon']); - $this->assertEquals(0, $item['order']); - } -} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index e068ffd..0000000 --- a/tests/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# nc_bot_webhooks Test Suite - -## Running Tests Locally - -```bash -cd nc_bot_webhooks -composer install -composer test -``` - -## Requirements - -- PHP 8.2+ -- Composer - -## What Each Test File Covers - -### `TalkServiceTest.php` - -Unit tests for `Service/TalkService.php` business logic. All TalkService constructor dependencies are mocked via `PHPUnit\Framework\TestCase::getMockForTrait()`. - -**Test groups:** - -- **validateBotPassword** — empty string, valid password, special chars, unicode -- **getBaseUrl** — overwritehost priority, overwritewebroot, trusted_domains filtering, CLI URL fallback, empty config -- **getRooms / setRooms** — JSON persistence round-trip, empty config, invalid JSON -- **getAuthTokens / setAuthTokens** — JSON persistence, empty config -- **validateAuthToken** — valid token, invalid token, nonexistent room, multiple tokens -- **generateAuthToken** — 48-char hex token, uniqueness across calls -- **revokeAuthToken** — removes specific token, cleans empty room arrays, nonexistent token -- **mapPayload** — content only, embeds with title/description/fields, empty payload, multiple embeds -- **mapApprisePayload** — body only, title fallback, type icons, image type with attachments, nested data key -- **getSenderName** — sender_name > username > config default priority -- **getSenderNameDefault** — config default -- **prependDisplayName** — with/without message, with type icon -- **downloadImage** — successful download, empty body -- **purgeOldImages** — files older/newer than cutoff, empty directory, bot user not found - -### `WebhookControllerTest.php` - -Unit tests for `Controller/WebhookController.php` HTTP endpoints. Tests response codes and headers for the controller methods that don't depend on `php://input` (auth validation and room listing), plus fallback paths that use `$_GET` and `$_POST` directly. - -**Test groups:** - -- **receive** — invalid auth token (401), empty body (400), $_GET fallback with content, $_GET fallback with embeds -- **receiveApprise** — invalid auth token (401), empty body (400), $_POST fallback with notifications, $_POST with image type, $_GET fallback with notifications -- **receiveAppriseNotify** — delegates to receiveApprise (401) -- **saveConfig** — error handling, invalid config (400), success with auth_tokens (200) -- **saveBotPassword** — invalid password (400), valid password (200) -- **getRooms** — empty rooms, room list with configured flag, exception → 500 -- **debug** — disabled (403), enabled for admin (200), enabled for non-admin (200) - -### `ImageCleanupTest.php` - -Unit tests for `Cron/ImageCleanup.php` cron job. Tests the full run() flow including early-exit paths and success logging. - -**Test groups:** - -- **Early exits** — no bot user, no images directory, root folder exception -- **Success** — purge runs and logs count, no log when count is 0 -- **Edge cases** — null argument handling - -### `NavigationProviderTest.php` - -Unit tests for `NavigationProvider` settings link. Tests that the navigation link is only shown to admin users. - -**Test groups:** - -- **Non-admin** — null user, non-admin user → empty array -- **Admin** — admin user → navigation link with correct metadata - -### `DebugToggleTest.php` - -Unit tests for `Command/DebugToggle.php` OCC command. Tests --status, --enable, --disable, toggle, and --enable/--disable conflict. - -**Test groups:** - -- **--status** — disabled by default, enabled when set -- **--enable** — sets value to true, logs warning -- **--disable** — sets value to false, logs success -- **Toggle** — enables when disabled, disables when enabled -- **Conflict** — --enable + --disable together returns INVALID (1) - -## Adding New Tests - -1. Determine which service or controller the new code belongs to. -2. Add test methods to the appropriate test file. -3. Use `makeTalkServiceMock()` (WebhookControllerTest) or the helper methods in TalkServiceTest to set up mocks. -4. Run `composer test` to verify. - -## Test Architecture - -- **TalkService dependencies**: All 17 constructor parameters are mocked using PHPUnit's `getMockForTrait()` for abstract/interface types and `createMock()` for concrete classes. -- **WebhookController dependencies**: TalkService is mocked; the other 9 dependencies use `createMock()`. -- **ImageCleanup dependencies**: TalkService, IRootFolder, IUserManager, LoggerInterface — all mocked. -- **NavigationProvider dependencies**: IURLGenerator, IUserSession, IConfig, IL10N — all mocked. -- **DebugToggle dependencies**: IAppConfig — mocked; Symfony Console Input/Output used for CLI interaction. -- **No real Nextcloud instance**: Tests verify business logic in isolation. Image upload/download tests verify the download logic but not real file storage. -- **php://input**: The controller reads from `php://input` which cannot be mocked directly. Tests cover: - - Auth-failure path (no input needed) - - `$_GET` fallback for receive (simulated via `$_GET` in test setup) - - `$_POST` fallback for receiveApprise (simulated via `$_POST` in test setup) - - Payload parsing / mapping logic is covered by TalkServiceTest. - -## CI - -Tests run automatically on every push and PR via Gitea Actions. See `.gitea/workflows/ci.yml`. - -## Test Summary - -| File | Tests | Coverage | -|------|-------|----------| -| TalkServiceTest.php | 48 | All TalkService methods | -| WebhookControllerTest.php | 21 | All controller endpoints + fallbacks | -| ImageCleanupTest.php | 6 | All run() paths | -| NavigationProviderTest.php | 3 | Admin/non-admin navigation | -| DebugToggleTest.php | 7 | --status, --enable, --disable, toggle, conflict | -| **Total** | **85** | All lib/ components covered | diff --git a/tests/TalkServiceTest.php b/tests/TalkServiceTest.php deleted file mode 100644 index 0c104cc..0000000 --- a/tests/TalkServiceTest.php +++ /dev/null @@ -1,987 +0,0 @@ -config; // the config mock — we swap it below - - // We need direct access to the crypto mock to configure encrypt/decrypt - // TalkServiceMockBuilder stores them as properties; we re-construct. - $cryptoMock = $this->createMock(\OCP\Security\ICrypto::class); - $cryptoMock->method('encrypt') - ->willReturnCallback(fn($s) => 'encrypted:' . $s); - $cryptoMock->method('decrypt') - ->willReturnCallback(fn($s) => substr($s, 10)); // strip 'encrypted:' - - // Build with our crypto mock - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $this->createMock(\OCP\IConfig::class), - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $cryptoMock, - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - return [$service, $cryptoMock]; - } - - // ── validateBotPassword ────────────────────────────────────── - - public function testValidateBotPasswordValid(): void { - $service = $this->makeService()[0]; - $result = $service->validateBotPassword('mySecurePassword123!'); - $this->assertTrue($result['valid']); - } - - public function testValidateBotPasswordEmpty(): void { - $service = $this->makeService()[0]; - $result = $service->validateBotPassword(''); - $this->assertFalse($result['valid']); - $this->assertStringContainsString('empty', $result['error']); - } - - public function testValidateBotPasswordSpecialChars(): void { - $service = $this->makeService()[0]; - $result = $service->validateBotPassword("!@#\$%^&*()_+-=[]{}|;:',.<>?/~`"); - $this->assertTrue($result['valid']); - } - - public function testValidateBotPasswordUnicode(): void { - $service = $this->makeService()[0]; - $result = $service->validateBotPassword('密码🔒🎉'); - $this->assertTrue($result['valid']); - } - - // ── getBaseUrl ─────────────────────────────────────────────── - - public function testGetBaseUrlOverwriteHostTakesPriority(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', 'example.com'], - ['overwriteproto', 'https', 'https'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('https://example.com', $service->getBaseUrl()); - } - - public function testGetBaseUrlOverwritewebrootFullPath(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', 'https://webroot.example.com/nextcloud'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('https://webroot.example.com/nextcloud', $service->getBaseUrl()); - } - - public function testGetBaseUrlOverwritewebrootPath(): void { - // Path-style overwritewebroot should fall through to trusted_domains - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', '/nextcloud'], - ]); - $trustedMock = $this->createMock(\OCP\IConfig::class); - $trustedMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', '/nextcloud'], - ]); - $trustedMock->method('getSystemValue') - ->willReturn(['public.example.com']); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $trustedMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('https://public.example.com', $service->getBaseUrl()); - } - - public function testGetBaseUrlSkipsPrivateDomains(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', ''], - ]); - $configMock->method('getSystemValue') - ->willReturn(['192.168.1.100', '10.0.0.5', 'public.example.com']); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('https://public.example.com', $service->getBaseUrl()); - } - - public function testGetBaseUrlFallbackToCliUrl(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', ''], - ]); - $configMock->method('getSystemValue') - ->willReturn([]); - $configMock->method('getSystemValueString') - ->willReturnMap([ - ['overwritehost', '', ''], - ['overwritewebroot', '', ''], - ['overwrite.cli.url', '', 'https://cli.example.com'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('https://cli.example.com', $service->getBaseUrl()); - } - - public function testGetBaseUrlEmptyConfig(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getSystemValueString')->willReturn(''); - $configMock->method('getSystemValue')->willReturn([]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame('', $service->getBaseUrl()); - } - - // ── getRooms / setRooms ────────────────────────────────────── - - public function testGetRoomsEmpty(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'rooms', '[]', '[]'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame([], $service->getRooms()); - } - - public function testGetRoomsParsed(): void { - $json = json_encode(['abc123' => 'General Chat', 'xyz789' => 'Builds']); - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'rooms', '[]', $json], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $rooms = $service->getRooms(); - $this->assertArrayHasKey('abc123', $rooms); - $this->assertSame('General Chat', $rooms['abc123']); - $this->assertArrayHasKey('xyz789', $rooms); - $this->assertSame('Builds', $rooms['xyz789']); - } - - public function testSetRoomsPersists(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'rooms', '[]', '[]'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $rooms = ['room1' => 'Room One']; - $configMock->expects($this->once()) - ->method('setAppValue') - ->with(TalkService::APP_ID, 'rooms', json_encode($rooms)); - - $service->setRooms($rooms); - } - - // ── getAuthTokens / setAuthTokens ──────────────────────────── - - public function testGetAuthTokensEmpty(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'auth_tokens', '{}', '{}'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $this->assertSame([], $service->getAuthTokens()); - } - - public function testGetAuthTokensParsed(): void { - $json = json_encode(['abc123' => ['tok1', 'tok2'], 'xyz789' => ['tok3']]); - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'auth_tokens', '{}', $json], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - $tokens = $service->getAuthTokens(); - $this->assertSame(['tok1', 'tok2'], $tokens['abc123']); - $this->assertSame(['tok3'], $tokens['xyz789']); - } - - public function testSetAuthTokensPersists(): void { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnMap([ - [TalkService::APP_ID, 'auth_tokens', '{}', '{}'], - ]); - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $tokens = ['abc123' => ['tok1']]; - $configMock->expects($this->once()) - ->method('setAppValue') - ->with(TalkService::APP_ID, 'auth_tokens', json_encode($tokens)); - - $service->setAuthTokens($tokens); - } - - // ── validateAuthToken ──────────────────────────────────────── - - public function testValidateAuthTokenValid(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1', 'tok2']])], - ]); - $this->assertTrue($service->validateAuthToken('abc123', 'tok1')); - $this->assertTrue($service->validateAuthToken('abc123', 'tok2')); - } - - public function testValidateAuthTokenInvalid(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1', 'tok2']])], - ]); - $this->assertFalse($service->validateAuthToken('abc123', 'tok3')); - } - - public function testValidateAuthTokenNonexistentRoom(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1']])], - ]); - $this->assertFalse($service->validateAuthToken('xyz789', 'tok1')); - } - - public function testValidateAuthTokenMultipleTokens(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1', 'tok2', 'tok3']])], - ]); - $this->assertTrue($service->validateAuthToken('abc123', 'tok2')); - } - - public function testValidateAuthTokenTokenNotInRoom(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1'], 'xyz789' => ['tok2']])], - ]); - $this->assertFalse($service->validateAuthToken('abc123', 'tok2')); - } - - /** - * Helper: build a TalkService with a configured IConfig mock. - * - * @param array $valueMap value map for IConfig::getAppValue - * @return array{0: TalkService, 1: \PHPUnit\Framework\MockObject\MockObject} - */ - private function makeServiceWithConfig(array $valueMap): array { - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue') - ->willReturnCallback(function ($app, $key, $default) use ($valueMap, $configMock) { - foreach ($valueMap as $entry) { - if ($entry[0] === $app && $entry[1] === $key) { - return $entry[3] ?? $default; - } - } - return $default; - }); - // setAppValue is a no-op in tests - $configMock->method('setAppValue') - ->willReturnCallback(fn($app, $key, $value) => null); - - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - return [$service, $configMock]; - } - - // ── generateAuthToken ──────────────────────────────────────── - - public function testGenerateAuthTokenGeneratesToken(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', '{}'], - ]); - $token = $service->generateAuthToken('abc123'); - $this->assertEquals(48, strlen($token)); - $this->assertRegExp('/^[0-9a-f]+$/', $token); - } - - public function testGenerateAuthTokenPersists(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', '{}'], - ]); - $configMock->expects($this->atLeast(2)) - ->method('setAppValue') - ->willReturnCallback(fn($app, $key, $value) => null); - - $token1 = $service->generateAuthToken('abc123'); - $token2 = $service->generateAuthToken('abc123'); - - $this->assertNotSame($token1, $token2); - } - - // ── revokeAuthToken ────────────────────────────────────────── - - public function testRevokeAuthTokenRemovesToken(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1', 'tok2']])], - ]); - $configMock->expects($this->atLeast(2)) - ->method('setAppValue') - ->willReturnCallback(fn($app, $key, $value) => null); - - $service->revokeAuthToken('abc123', 'tok1'); - - $this->assertFalse($service->validateAuthToken('abc123', 'tok1')); - $this->assertTrue($service->validateAuthToken('abc123', 'tok2')); - } - - public function testRevokeAuthTokenCleansEmptyRoom(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1']])], - ]); - $configMock->expects($this->atLeast(2)) - ->method('setAppValue') - ->willReturnCallback(fn($app, $key, $value) => null); - - $service->revokeAuthToken('abc123', 'tok1'); - - $tokens = $service->getAuthTokens(); - $this->assertArrayNotHasKey('abc123', $tokens); - } - - public function testRevokeAuthTokenNonexistentToken(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'auth_tokens', '{}', json_encode(['abc123' => ['tok1']])], - ]); - $configMock->expects($this->once()) - ->method('setAppValue') - ->willReturnCallback(fn($app, $key, $value) => null); - - $service->revokeAuthToken('abc123', 'nonexistent'); - - // Should still have tok1 - $this->assertTrue($service->validateAuthToken('abc123', 'tok1')); - } - - // ── mapPayload ─────────────────────────────────────────────── - - public function testMapPayloadContentOnly(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload(['content' => 'Hello world']); - $this->assertSame('Hello world', $result); - } - - public function testMapPayloadEmbedWithTitle(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload([ - 'embeds' => [['title' => 'Build #123']], - ]); - $this->assertStringContainsString('**Build #123**', $result); - } - - public function testMapPayloadEmbedWithDescription(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload([ - 'embeds' => [['description' => 'Passed']], - ]); - $this->assertStringContainsString('Passed', $result); - } - - public function testMapPayloadEmbedWithFields(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload([ - 'embeds' => [[ - 'fields' => [ - ['name' => 'Duration', 'value' => '2m 34s'], - ['name' => 'Environment', 'value' => 'Production'], - ], - ]], - ]); - $this->assertStringContainsString('Duration: 2m 34s', $result); - $this->assertStringContainsString('Environment: Production', $result); - } - - public function testMapPayloadEmpty(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload([]); - $this->assertSame('', $result); - } - - public function testMapPayloadMultipleEmbeds(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->mapPayload([ - 'embeds' => [ - ['title' => 'First'], - ['title' => 'Second'], - ], - ]); - $this->assertStringContainsString('**First**', $result); - $this->assertStringContainsString('**Second**', $result); - } - - // ── mapApprisePayload ──────────────────────────────────────── - - public function testMapApprisePayloadBodyOnly(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'Custom Bot'], - ]); - $result = $service->mapApprisePayload(['body' => 'All clear'], 'abc123'); - $this->assertSame('All clear', $result['message']); - } - - public function testMapApprisePayloadTitleFallback(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'Default Sender'], - ]); - $result = $service->mapApprisePayload(['title' => 'Alert', 'body' => 'Something happened'], 'abc123'); - $this->assertSame('Something happened', $result['message']); - $this->assertSame('Alert', $result['senderName']); - } - - public function testMapApprisePayloadTypeIcons(): void { - [$service, $configMock] = $this->makeServiceWithConfig([]); - // Success type — should include ✅ icon - $resultSuccess = $service->mapApprisePayload(['body' => 'OK', 'type' => 'success'], 'abc123'); - $this->assertStringContainsString('✅', $resultSuccess['message']); - - // Warning type - $resultWarning = $service->mapApprisePayload(['body' => 'Warn', 'type' => 'warning'], 'abc123'); - $this->assertStringContainsString('⚠️', $resultWarning['message']); - - // Error type - $resultError = $service->mapApprisePayload(['body' => 'Fail', 'type' => 'error'], 'abc123'); - $this->assertStringContainsString('❌', $resultError['message']); - } - - public function testMapApprisePayloadImageType(): void { - [$service, $configMock] = $this->makeServiceWithConfig([]); - // Image type with attachment URLs — processImageUrls is called, which returns empty - // because there's no real file system; verify richObjects is set - $result = $service->mapApprisePayload([ - 'body' => 'Check this out', - 'type' => 'image', - 'attachments' => [['url' => 'https://example.com/img.png']], - ], 'abc123'); - // richObjects should be an array (possibly empty due to no real FS) - $this->assertIsArray($result['richObjects']); - } - - public function testMapApprisePayloadNestedDataKey(): void { - [$service, $configMock] = $this->makeServiceWithConfig([]); - // When the payload is wrapped in a 'data' key (Home Assistant), the controller - // unwraps it before passing to mapApprisePayload. Here we test the unwrapped form. - $result = $service->mapApprisePayload([ - 'body' => 'Direct payload', - 'title' => 'Direct Title', - ], 'abc123'); - $this->assertSame('Direct payload', $result['message']); - $this->assertSame('Direct Title', $result['senderName']); - } - - // ── getSenderName ──────────────────────────────────────────── - - public function testGetSenderNameSenderNameTakesPriority(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'Default Sender'], - ]); - $this->assertSame('Custom', $service->getSenderName(['sender_name' => 'Custom'])); - } - - public function testGetSenderNameUsernameFallback(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'Default Sender'], - ]); - $this->assertSame('CI Bot', $service->getSenderName(['username' => 'CI Bot'])); - } - - public function testGetSenderNameConfigDefault(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'Default Sender'], - ]); - $this->assertSame('Default Sender', $service->getSenderName([])); - } - - // ── getSenderNameDefault ───────────────────────────────────── - - public function testGetSenderNameDefault(): void { - [$service, $configMock] = $this->makeServiceWithConfig([ - [TalkService::APP_ID, 'sender_name', 'Webhook Bot', 'My Bot'], - ]); - $this->assertSame('My Bot', $service->getSenderNameDefault()); - } - - // ── prependDisplayName ─────────────────────────────────────── - - public function testPrependDisplayNameWithMessage(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->prependDisplayName('CI Bot', 'Build passed'); - $this->assertStringContainsString('**CI Bot**', $result); - $this->assertStringContainsString('Build passed', $result); - $this->assertStringContainsString("\n\n", $result); - } - - public function testPrependDisplayNameWithoutMessage(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->prependDisplayName('CI Bot', ''); - $this->assertStringContainsString('**CI Bot**', $result); - $this->assertStringNotContainsString("\n\n", $result); - } - - public function testPrependDisplayNameWithTypeIcon(): void { - [$service] = $this->makeServiceWithConfig([]); - $result = $service->prependDisplayName('CI Bot', 'OK', '✅'); - $this->assertStringContainsString('✅', $result); - $this->assertStringContainsString('**CI Bot**', $result); - } - - // ── downloadImage ──────────────────────────────────────────── - - public function testDownloadImageSuccessful(): void { - [$service, $configMock] = $this->makeServiceWithConfig([]); - - $responseMock = $this->createMock(\Psr\Http\Message\ResponseInterface::class); - $responseMock->method('getBody') - ->willReturn(new class { - public function __toString(): string { return 'image-data'; } - }); - - $clientMock = $this->createMock(\OCP\Http\Client\IClient::class); - $clientMock->method('get') - ->willReturn($responseMock); - - $clientServiceMock = $this->createMock(\OCP\Http\Client\IClientService::class); - $clientServiceMock->method('getClient') - ->willReturn($clientMock); - - // Rebuild with our client service - $configMock2 = $this->createMock(\OCP\IConfig::class); - $configMock2->method('getAppValue')->willReturn(''); - $service2 = new TalkService( - $clientServiceMock, - $configMock2, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $result = $service2->downloadImage('https://example.com/image.png'); - $this->assertSame('image-data', $result); - } - - public function testDownloadImageEmptyBody(): void { - [$service] = $this->makeServiceWithConfig([]); - - $responseMock = $this->createMock(\Psr\Http\Message\ResponseInterface::class); - $responseMock->method('getBody') - ->willReturn(new class { - public function __toString(): string { return ''; } - }); - - $clientMock = $this->createMock(\OCP\Http\Client\IClient::class); - $clientMock->method('get') - ->willReturn($responseMock); - - $clientServiceMock = $this->createMock(\OCP\Http\Client\IClientService::class); - $clientServiceMock->method('getClient') - ->willReturn($clientMock); - - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue')->willReturn(''); - $service2 = new TalkService( - $clientServiceMock, - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $this->createMock(\OCP\Files\IRootFolder::class), - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $this->createMock(\OCP\IUserManager::class), - $this->createMock(\OCP\IUserSession::class), - $this->createMock(\Psr\Log\LoggerInterface::class), - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $result = $service2->downloadImage('https://example.com/empty.png'); - $this->assertSame('', $result); - } - - // ── purgeOldImages ─────────────────────────────────────────── - - public function testPurgeOldImagesWithFiles(): void { - // Create a temporary directory to simulate the images folder - $tempDir = sys_get_temp_dir() . '/nc_bot_webhooks_test_' . uniqid(); - mkdir($tempDir . '/abc123', 0755, true); - - // Create a file older than 90 days - $oldFile = $tempDir . '/abc123/old.png'; - file_put_contents($oldFile, 'data'); - touch($oldFile, time() - (100 * 86400)); // 100 days ago - - // Create a recent file - $recentFile = $tempDir . '/abc123/recent.png'; - file_put_contents($recentFile, 'data'); - touch($recentFile, time() - (10 * 86400)); // 10 days ago - - // Mock rootFolder to return our temp directory - $folderMock = $this->createMock(\OCP\Files\Folder::class); - $folderMock->method('getContents') - ->willReturn(['old.png', 'recent.png']); - - $oldFileMock = $this->createMock(\OCP\Files\File::class); - $oldFileMock->method('getMTime') - ->willReturn(time() - (100 * 86400)); - - $recentFileMock = $this->createMock(\OCP\Files\File::class); - $recentFileMock->method('getMTime') - ->willReturn(time() - (10 * 86400)); - - $folderMock->method('get') - ->willReturnMap([ - ['old.png', $oldFileMock], - ['recent.png', $recentFileMock], - ]); - - $rootMock = $this->createMock(\OCP\Files\IRootFolder::class); - $rootMock->method('getUserFolder') - ->willReturn($folderMock); - - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue')->willReturn('90'); - - $userManagerMock = $this->createMock(\OCP\IUserManager::class); - $userMock = $this->createMock(\OCP\IUser::class); - $userMock->method('getUID') - ->willReturn('talk-bot'); - $userManagerMock->method('get') - ->willReturn($userMock); - - $loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $rootMock, - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $userManagerMock, - $this->createMock(\OCP\IUserSession::class), - $loggerMock, - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $count = $service->purgeOldImages(); - $this->assertGreaterThanOrEqual(1, $count); - - // Cleanup - unlink($oldFile); - unlink($recentFile); - rmdir($tempDir . '/abc123'); - rmdir($tempDir); - } - - public function testPurgeOldImagesEmptyDirectory(): void { - $folderMock = $this->createMock(\OCP\Files\Folder::class); - $folderMock->method('getContents') - ->willReturn([]); - - $rootMock = $this->createMock(\OCP\Files\IRootFolder::class); - $rootMock->method('getUserFolder') - ->willReturn($folderMock); - - $configMock = $this->createMock(\OCP\IConfig::class); - $configMock->method('getAppValue')->willReturn('90'); - - $userManagerMock = $this->createMock(\OCP\IUserManager::class); - $userMock = $this->createMock(\OCP\IUser::class); - $userMock->method('getUID') - ->willReturn('talk-bot'); - $userManagerMock->method('get') - ->willReturn($userMock); - - $loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - - $service = new TalkService( - $this->createMock(\OCP\Http\Client\IClientService::class), - $configMock, - $this->createMock(\OCP\IDBConnection::class), - $rootMock, - $this->createMock(\OCP\IRequest::class), - $this->createMock(\OCP\IURLGenerator::class), - $userManagerMock, - $this->createMock(\OCP\IUserSession::class), - $loggerMock, - $this->createMock(\OCA\Talk\Manager::class), - $this->createMock(\OCP\Security\ICrypto::class), - $this->createMock(\OCA\Talk\Model\AttendeeMapper::class), - $this->createMock(\OCP\Share\IManager::class), - $this->createMock(\OCA\Talk\Service\ParticipantService::class), - $this->createMock(\OCA\Talk\Chat\ChatManager::class), - $this->createMock(\OCA\Talk\TalkSession::class), - ); - - $count = $service->purgeOldImages(); - $this->assertSame(0, $count); - } -} diff --git a/tests/WebhookControllerTest.php b/tests/WebhookControllerTest.php deleted file mode 100644 index 11c8f93..0000000 --- a/tests/WebhookControllerTest.php +++ /dev/null @@ -1,613 +0,0 @@ -createMock(IRequest::class), - $talkService, - $this->createMock(LoggerInterface::class), - $this->createMock(IAppManager::class), - $this->createMock(IUserSession::class), - $this->createMock(IGroupManager::class), - $this->createMock(IConfig::class), - $this->createMock(IAppConfig::class), - $this->createMock(IClientService::class), - $this->createMock(IShareManager::class), - ); - } - - /** - * Build a TalkService mock with given method return values. - */ - private function makeTalkServiceMock(array $methods): TalkService { - $mock = $this->createMock(TalkService::class); - foreach ($methods as $method => $return) { - if (is_callable($return)) { - $mock->method($method) - ->willReturnCallback($return); - } else { - $mock->method($method) - ->willReturn($return); - } - } - return $mock; - } - - // ========================================================================= - // receive (Discord webhook) - // ========================================================================= - - public function testReceiveReturns401ForInvalidAuthToken(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => false, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->receive('room123', 'badtoken'); - - $this->assertEquals(401, $response->getStatus()); - $this->assertEquals('Unauthorized', $response->getData()['error']); - } - - public function testReceiveReturns400ForEmptyBody(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => true, - ]); - $controller = $this->makeController($talkService); - - // With no php://input available, json_decode('') fails → 400 - $response = $controller->receive('room123', 'validtoken'); - - $this->assertEquals(400, $response->getStatus()); - $this->assertEquals('Invalid JSON', $response->getData()['error']); - } - - public function testReceiveUsesGETFallbackWhenInputIsEmpty(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => true, - 'mapPayload' => function ($payload) { - return [ - 'message' => $payload['content'] ?? '', - 'senderName' => 'Bot', - 'displayName' => 'Bot', - 'richObjects' => [], - ]; - }, - 'getSenderNameDefault' => 'Bot', - 'prependDisplayName' => function ($name, $msg) { - return $msg; - }, - 'postToRoom' => true, - ]); - - $controller = $this->makeController($talkService); - - // Simulate empty php://input with $_GET fallback - $_GET = [ - 'content' => 'GET fallback message', - ]; - - $response = $controller->receive('room123', 'validtoken'); - - $this->assertEquals(201, $response->getStatus()); - $this->assertEquals('ok', $response->getData()['status']); - - // Clean up - $_GET = []; - } - - public function testReceiveGETFallbackWithEmbeds(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateAuthToken') - ->willReturn(true); - $talkService->method('mapPayload') - ->willReturnCallback(function ($payload) { - return [ - 'message' => $payload['content'] ?? '', - 'senderName' => 'Bot', - 'displayName' => 'Bot', - 'richObjects' => [], - ]; - }); - $talkService->method('getSenderNameDefault') - ->willReturn('Bot'); - $talkService->method('prependDisplayName') - ->willReturnCallback(function ($name, $msg) { - return $msg; - }); - $talkService->method('postToRoom') - ->willReturn(true); - - $controller = $this->makeController($talkService); - - $_GET = [ - 'content' => 'Test with embeds', - 'embeds' => '[{"title":"Embed Title","description":"Embed desc"}]', - ]; - - $response = $controller->receive('room123', 'validtoken'); - - $this->assertEquals(201, $response->getStatus()); - - // Clean up - $_GET = []; - } - - // ========================================================================= - // receiveApprise - // ========================================================================= - - public function testReceiveAppriseReturns401ForInvalidAuthToken(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => false, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->receiveApprise('room123', 'badtoken'); - - $this->assertEquals(401, $response->getStatus()); - $this->assertEquals('Unauthorized', $response->getData()['error']); - } - - public function testReceiveAppriseReturns400ForEmptyBody(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => true, - ]); - $controller = $this->makeController($talkService); - - // No php://input → no data → 400 - $response = $controller->receiveApprise('room123', 'validtoken'); - - $this->assertEquals(400, $response->getStatus()); - } - - public function testReceiveAppriseNotifyDelegatesToReceiveApprise(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateAuthToken' => false, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->receiveAppriseNotify('room123', 'badtoken'); - - $this->assertEquals(401, $response->getStatus()); - $this->assertEquals('Unauthorized', $response->getData()['error']); - } - - public function testReceiveAppriseUsesPOSTFallbackWithNotifications(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateAuthToken') - ->willReturn(true); - $talkService->method('mapApprisePayload') - ->willReturn([ - 'message' => 'POST fallback message', - 'senderName' => 'Apprise', - 'displayName' => 'Apprise', - 'richObjects' => [], - ]); - $talkService->method('getSenderNameDefault') - ->willReturn('Bot'); - $talkService->method('prependDisplayName') - ->willReturnCallback(function ($name, $msg) { - return $msg; - }); - $talkService->method('postToRoom') - ->willReturn(true); - - $controller = $this->makeController($talkService); - - // Simulate $_POST fallback (form-encoded with notifications wrapper) - $_POST = [ - 'notifications' => [ - [ - 'body' => 'POST fallback message', - 'type' => 'success', - ], - ], - ]; - - $response = $controller->receiveApprise('room123', 'validtoken'); - - $this->assertEquals(201, $response->getStatus()); - - // Clean up - $_POST = []; - } - - public function testReceiveApprisePOSTFallbackWithImageType(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateAuthToken') - ->willReturn(true); - $talkService->method('mapApprisePayload') - ->willReturn([ - 'message' => '', - 'senderName' => 'Apprise', - 'displayName' => 'Apprise', - 'richObjects' => ['richObject' => []], - ]); - $talkService->method('getSenderNameDefault') - ->willReturn('Bot'); - $talkService->method('prependDisplayName') - ->willReturnCallback(function ($name, $msg) { - return $msg; - }); - $talkService->method('postToRoom') - ->willReturn(true); - - $controller = $this->makeController($talkService); - - $_POST = [ - 'notifications' => [ - [ - 'body' => 'Image notification', - 'type' => 'image', - 'attachments' => ['https://example.com/image.png'], - ], - ], - ]; - - $response = $controller->receiveApprise('room123', 'validtoken'); - - $this->assertEquals(201, $response->getStatus()); - - // Clean up - $_POST = []; - } - - public function testReceiveAppriseGETFallbackWithNotifications(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateAuthToken') - ->willReturn(true); - $talkService->method('mapApprisePayload') - ->willReturn([ - 'message' => 'GET fallback message', - 'senderName' => 'Apprise', - 'displayName' => 'Apprise', - 'richObjects' => [], - ]); - $talkService->method('getSenderNameDefault') - ->willReturn('Bot'); - $talkService->method('prependDisplayName') - ->willReturnCallback(function ($name, $msg) { - return $msg; - }); - $talkService->method('postToRoom') - ->willReturn(true); - - $controller = $this->makeController($talkService); - - // $_GET with notifications array - $_GET = [ - 'notifications' => [ - [ - 'body' => 'GET fallback message', - 'type' => 'notice', - ], - ], - ]; - - $response = $controller->receiveApprise('room123', 'validtoken'); - - $this->assertEquals(201, $response->getStatus()); - - // Clean up - $_GET = []; - } - - // ========================================================================= - // saveConfig / saveBotPassword - // ========================================================================= - - public function testSaveConfigReturns500WhenTalkServiceFails(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateBotPassword') - ->willThrowException(new \Exception('crypto error')); - - $controller = $this->makeController($talkService); - - $response = $controller->saveConfig([ - 'bot_password' => 'test', - 'retention_days' => '90', - 'sender_name' => 'Bot', - 'rooms' => [], - 'auth_tokens' => [], - ]); - - $this->assertEquals(500, $response->getStatus()); - } - - public function testSaveConfigReturns400WhenInvalidConfig(): void { - $talkService = $this->createMock(TalkService::class); - - $controller = $this->makeController($talkService); - - $response = $controller->saveConfig(null); - - $this->assertEquals(400, $response->getStatus()); - $this->assertEquals('Invalid config', $response->getData()['error']); - } - - public function testSaveConfigReturns200WithAuthTokens(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('validateBotPassword') - ->willReturn(true); - $talkService->method('saveConfig') - ->willReturn(null); - $talkService->method('getAuthTokens') - ->willReturn(['room1' => 'token1']); - - $controller = $this->makeController($talkService); - - $response = $controller->saveConfig([ - 'bot_password' => 'validpassword', - 'retention_days' => '30', - 'sender_name' => 'TestBot', - 'rooms' => ['room1'], - 'auth_tokens' => ['room1' => 'token1'], - ]); - - $this->assertEquals(200, $response->getStatus()); - $this->assertEquals('ok', $response->getData()['status']); - $this->assertEquals(['room1' => 'token1'], $response->getData()['auth_tokens']); - } - - public function testSaveBotPasswordRejectsInvalidPassword(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateBotPassword' => false, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->saveBotPassword('invalid'); - - $this->assertEquals(400, $response->getStatus()); - } - - public function testSaveBotPasswordAcceptsValidPassword(): void { - $talkService = $this->makeTalkServiceMock([ - 'validateBotPassword' => true, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->saveBotPassword('validpassword123'); - - $this->assertEquals(200, $response->getStatus()); - } - - // ========================================================================= - // getRooms - // ========================================================================= - - public function testGetRoomsReturnsEmptyArrayWhenNoRooms(): void { - $talkService = $this->makeTalkServiceMock([ - 'getAvailableTalkRooms' => [], - 'getRooms' => [], - 'detectTalkTableFromCatalog' => null, - ]); - $controller = $this->makeController($talkService); - - $response = $controller->getRooms(); - - $this->assertEquals(200, $response->getStatus()); - $this->assertEquals([], $response->getData()); - } - - public function testGetRoomsReturnsRoomListWithConfiguredFlag(): void { - $talkService = $this->makeTalkServiceMock([ - 'getAvailableTalkRooms' => [ - 'abc123' => 'Test Room', - 'def456' => 'Another Room', - ], - 'getRooms' => ['abc123' => true], // only abc123 configured - 'detectTalkTableFromCatalog' => 'talk_rooms', - ]); - - $dbConn = $this->createMock(\Doctrine\DBAL\Connection::class); - $dbConn->method('executeQuery') - ->willReturn(null); - $talkService->method('getDbConnection') - ->willReturn($dbConn); - - $controller = $this->makeController($talkService); - - $response = $controller->getRooms(); - - $this->assertEquals(200, $response->getStatus()); - $data = $response->getData(); - $this->assertNotEmpty($data); - - $roomA = null; - $roomB = null; - foreach ($data as $room) { - if ($room['token'] === 'abc123') $roomA = $room; - if ($room['token'] === 'def456') $roomB = $room; - } - - $this->assertNotNull($roomA); - $this->assertTrue($roomA['configured']); - $this->assertTrue(isset($roomA['token'])); - $this->assertTrue(isset($roomA['name'])); - $this->assertTrue(isset($roomA['type_label'])); - - $this->assertNotNull($roomB); - $this->assertFalse($roomB['configured']); - } - - public function testGetRoomsReturns500OnException(): void { - $talkService = $this->createMock(TalkService::class); - $talkService->method('getAvailableTalkRooms') - ->willThrowException(new \Exception('DB connection failed')); - - $controller = $this->makeController($talkService); - - $response = $controller->getRooms(); - - $this->assertEquals(500, $response->getStatus()); - $this->assertStringContainsString('DB connection failed', $response->getData()['error']); - } - - // ========================================================================= - // debug endpoint - // ========================================================================= - - public function testDebugReturns403WhenDisabled(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(false); - - $talkService = $this->createMock(TalkService::class); - $controller = new WebhookController( - 'nc_bot_webhooks', - $this->createMock(IRequest::class), - $talkService, - $this->createMock(LoggerInterface::class), - $this->createMock(IAppManager::class), - $this->createMock(IUserSession::class), - $this->createMock(IGroupManager::class), - $this->createMock(IConfig::class), - $appConfig, - $this->createMock(IClientService::class), - $this->createMock(IShareManager::class), - ); - - $response = $controller->debug(); - - $this->assertEquals(403, $response->getStatus()); - $this->assertStringContainsString('disabled', $response->getData()['error']); - } - - public function testDebugReturns200WhenEnabled(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(true); - - $talkService = $this->createMock(TalkService::class); - $talkService->method('getBotUser') - ->willReturn(null); - $talkService->method('hasBotPassword') - ->willReturn(false); - - $user = $this->createMock(IUser::class); - $user->method('getUID') - ->willReturn('admin'); - $user->method('isAdmin') - ->willReturn(true); - - $userSession = $this->createMock(IUserSession::class); - $userSession->method('getUser') - ->willReturn($user); - - $groupManager = $this->createMock(IGroupManager::class); - $groupManager->method('isAdmin') - ->willReturn(true); - - $config = $this->createMock(IConfig::class); - $config->method('getSystemValueString') - ->willReturn(''); - - $controller = new WebhookController( - 'nc_bot_webhooks', - $this->createMock(IRequest::class), - $talkService, - $this->createMock(LoggerInterface::class), - $this->createMock(IAppManager::class), - $userSession, - $groupManager, - $config, - $appConfig, - $this->createMock(IClientService::class), - $this->createMock(IShareManager::class), - ); - - $response = $controller->debug(); - - $this->assertEquals(200, $response->getStatus()); - $data = $response->getData(); - $this->assertTrue($data['debug_enabled']); - $this->assertEquals('admin', $data['user']); - $this->assertTrue($data['user_is_admin']); - } - - public function testDebugNonAdminUser(): void { - $appConfig = $this->createMock(IAppConfig::class); - $appConfig->method('getValueBool') - ->willReturn(true); - - $talkService = $this->createMock(TalkService::class); - $talkService->method('getBotUser') - ->willReturn(null); - $talkService->method('hasBotPassword') - ->willReturn(false); - - $user = $this->createMock(IUser::class); - $user->method('getUID') - ->willReturn('regular_user'); - $user->method('isAdmin') - ->willReturn(false); - - $userSession = $this->createMock(IUserSession::class); - $userSession->method('getUser') - ->willReturn($user); - - $groupManager = $this->createMock(IGroupManager::class); - $groupManager->method('isAdmin') - ->willReturn(false); - - $config = $this->createMock(IConfig::class); - $config->method('getSystemValueString') - ->willReturn(''); - - $controller = new WebhookController( - 'nc_bot_webhooks', - $this->createMock(IRequest::class), - $talkService, - $this->createMock(LoggerInterface::class), - $this->createMock(IAppManager::class), - $userSession, - $groupManager, - $config, - $appConfig, - $this->createMock(IClientService::class), - $this->createMock(IShareManager::class), - ); - - $response = $controller->debug(); - - $this->assertEquals(200, $response->getStatus()); - $this->assertFalse($response->getData()['user_is_admin']); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index e37d29e..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,137 +0,0 @@ -build(); // all defaults - * $service = $builder->config(['app' => ['key' => 'val']])->build(); - * $service = $builder->botUser($botUser)->build(); - */ -class TalkServiceMockBuilder { - private \PHPUnit\Framework\MockObject\MockObject| \OCP\Http\Client\IClientService $clientService; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IConfig $config; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IDBConnection $db; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\Files\IRootFolder $rootFolder; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IRequest $request; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IURLGenerator $urlGenerator; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IUserManager $userManager; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\IUserSession $userSession; - private \PHPUnit\Framework\MockObject\MockObject| \Psr\Log\LoggerInterface $logger; - private \PHPUnit\Framework\MockObject\MockObject| \OCA\Talk\Manager $talkManager; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\Security\ICrypto $crypto; - private \PHPUnit\Framework\MockObject\MockObject| \OCA\Talk\Model\AttendeeMapper $attendeeMapper; - private \PHPUnit\Framework\MockObject\MockObject| \OCP\Share\IManager $shareManager; - private \PHPUnit\Framework\MockObject\MockObject| \OCA\Talk\Service\ParticipantService $participantService; - private \PHPUnit\Framework\MockObject\MockObject| \OCA\Talk\Chat\ChatManager $chatManager; - private \PHPUnit\Framework\MockObject\MockObject| \OCA\Talk\TalkSession $talkSession; - - public function __construct() { - $this->clientService = $this->makeMock(\OCP\Http\Client\IClientService::class); - $this->config = $this->makeMock(\OCP\IConfig::class); - $this->db = $this->makeMock(\OCP\IDBConnection::class); - $this->rootFolder = $this->makeMock(\OCP\Files\IRootFolder::class); - $this->request = $this->makeMock(\OCP\IRequest::class); - $this->urlGenerator = $this->makeMock(\OCP\IURLGenerator::class); - $this->userManager = $this->makeMock(\OCP\IUserManager::class); - $this->userSession = $this->makeMock(\OCP\IUserSession::class); - $this->logger = $this->makeMock(\Psr\Log\LoggerInterface::class); - $this->talkManager = $this->makeMock(\OCA\Talk\Manager::class); - $this->crypto = $this->makeMock(\OCP\Security\ICrypto::class); - $this->attendeeMapper = $this->makeMock(\OCA\Talk\Model\AttendeeMapper::class); - $this->shareManager = $this->makeMock(\OCP\Share\IManager::class); - $this->participantService = $this->makeMock(\OCA\Talk\Service\ParticipantService::class); - $this->chatManager = $this->makeMock(\OCA\Talk\Chat\ChatManager::class); - $this->talkSession = $this->makeMock(\OCA\Talk\TalkSession::class); - } - - private function makeMock(string $interface): \PHPUnit\Framework\MockObject\MockObject { - return \PHPUnit\Framework\TestCase::getMockForTrait($interface); - } - - /** Configure the config mock so getAppValue / setAppValue work as expected. */ - public function config(array $appValues = []): self { - // getMockForTrait returns a partial mock; we can still use willReturn / withConsecutive - $mock = $this->config; - if (is_object($mock) && method_exists($mock, 'expects')) { - // We set expectations lazily inside each test; this is just a convenience - // to pre-configure default return values. - } - return $this; - } - - /** - * Assemble and return a TalkService. - * - * @return \OCA\Ncbotwebhooks\Service\TalkService - */ - public function build(): \OCA\Ncbotwebhooks\Service\TalkService { - return new \OCA\Ncbotwebhooks\Service\TalkService( - $this->clientService, - $this->config, - $this->db, - $this->rootFolder, - $this->request, - $this->urlGenerator, - $this->userManager, - $this->userSession, - $this->logger, - $this->talkManager, - $this->crypto, - $this->attendeeMapper, - $this->shareManager, - $this->participantService, - $this->chatManager, - $this->talkSession, - ); - } -} - -}