Skip to content

Commit 6ee3d6f

Browse files
committed
test: added more controller tests
1 parent 4cabf7f commit 6ee3d6f

43 files changed

Lines changed: 1711 additions & 3 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tests/phpMyFAQ/Controller/AbstractControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use phpMyFAQ\Permission\BasicPermission;
99
use phpMyFAQ\User\CurrentUser;
1010
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
11+
use PHPUnit\Framework\Attributes\CoversClass;
12+
use PHPUnit\Framework\Attributes\UsesNamespace;
1113
use PHPUnit\Framework\Attributes\DataProvider;
1214
use PHPUnit\Framework\TestCase;
1315
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -20,6 +22,8 @@
2022
use Twig\TwigFilter;
2123

2224
#[AllowMockObjectsWithoutExpectations]
25+
#[CoversClass(AbstractController::class)]
26+
#[UsesNamespace('phpMyFAQ')]
2327
class AbstractControllerTest extends TestCase
2428
{
2529
private AbstractController $abstractController;

tests/phpMyFAQ/Controller/Administration/Api/ApiKeyControllerTest.php

Lines changed: 186 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use phpMyFAQ\Configuration;
99
use phpMyFAQ\Core\Exception;
1010
use phpMyFAQ\Database;
11+
use phpMyFAQ\Database\DatabaseDriver;
1112
use phpMyFAQ\Database\Sqlite3;
1213
use phpMyFAQ\Enums\PermissionType;
1314
use phpMyFAQ\Language;
@@ -332,6 +333,76 @@ public function testCreateReturnsBadRequestForInvalidExpiresAtWhenAuthenticated(
332333
self::assertSame('Invalid expiresAt value.', $payload['error']);
333334
}
334335

336+
/**
337+
* @throws \Exception
338+
*/
339+
public function testCreateReturnsUnauthorizedForInvalidCsrfWhenAuthenticated(): void
340+
{
341+
$controller = $this->createController();
342+
$controller->setContainer($this->createAuthenticatedContainer());
343+
344+
$response = $controller->create(new Request([], [], [], [], [], [], json_encode([
345+
'csrf' => 'invalid-token',
346+
'name' => 'Generated key',
347+
], JSON_THROW_ON_ERROR)));
348+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
349+
350+
self::assertSame(Response::HTTP_UNAUTHORIZED, $response->getStatusCode());
351+
self::assertSame(Translation::get('msgNoPermission'), $payload['error']);
352+
}
353+
354+
/**
355+
* @throws \Exception
356+
*/
357+
public function testCreateReturnsBadRequestForMissingNameWhenAuthenticated(): void
358+
{
359+
$controller = $this->createController();
360+
$container = $this->createAuthenticatedContainer();
361+
$session = $container->get('session');
362+
self::assertInstanceOf(Session::class, $session);
363+
$token = $this->createValidCsrfToken($session, 'api-key-create');
364+
$controller->setContainer($container);
365+
366+
$response = $controller->create(new Request([], [], [], [], [], [], json_encode([
367+
'csrf' => $token,
368+
'name' => '',
369+
], JSON_THROW_ON_ERROR)));
370+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
371+
372+
self::assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode());
373+
self::assertSame('API key name is required.', $payload['error']);
374+
}
375+
376+
/**
377+
* @throws \Exception
378+
*/
379+
public function testCreateReturnsInternalServerErrorWhenInsertFails(): void
380+
{
381+
$db = $this->createMock(DatabaseDriver::class);
382+
$db->method('escape')->willReturnCallback(static fn(string $value): string => $value);
383+
$db->method('nextId')->willReturn(5);
384+
$db->method('now')->willReturn("'2026-03-15 12:00:00'");
385+
$db->method('query')->willReturn(false);
386+
$db->method('error')->willReturn('insert failed');
387+
388+
$controller = $this->createController();
389+
$container = $this->createAuthenticatedContainerWithDb($db);
390+
$session = $container->get('session');
391+
self::assertInstanceOf(Session::class, $session);
392+
$token = $this->createValidCsrfToken($session, 'api-key-create');
393+
$controller->setContainer($container);
394+
395+
$response = $controller->create(new Request([], [], [], [], [], [], json_encode([
396+
'csrf' => $token,
397+
'name' => 'Generated key',
398+
'scopes' => ['faq.read'],
399+
], JSON_THROW_ON_ERROR)));
400+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
401+
402+
self::assertSame(Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode());
403+
self::assertSame('insert failed', $payload['error']);
404+
}
405+
335406
/**
336407
* @throws \Exception
337408
*/
@@ -386,6 +457,85 @@ public function testUpdateReturnsUpdatedApiKeyWhenAuthenticated(): void
386457
self::assertSame('2026-04-01 12:00:00', $payload['expiresAt']);
387458
}
388459

460+
/**
461+
* @throws \Exception
462+
*/
463+
public function testUpdateReturnsBadRequestForMissingIdWhenAuthenticated(): void
464+
{
465+
$controller = $this->createController();
466+
$container = $this->createAuthenticatedContainer();
467+
$session = $container->get('session');
468+
self::assertInstanceOf(Session::class, $session);
469+
$token = $this->createValidCsrfToken($session, 'api-key-update');
470+
$controller->setContainer($container);
471+
472+
$response = $controller->update(new Request([], [], [], [], [], [], json_encode([
473+
'csrf' => $token,
474+
'name' => 'Updated key',
475+
], JSON_THROW_ON_ERROR)));
476+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
477+
478+
self::assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode());
479+
self::assertSame('API key ID is required.', $payload['error']);
480+
}
481+
482+
/**
483+
* @throws \Exception
484+
*/
485+
public function testUpdateReturnsBadRequestForInvalidExpiresAtWhenAuthenticated(): void
486+
{
487+
$this->seedApiKeyRow();
488+
489+
$controller = $this->createController();
490+
$container = $this->createAuthenticatedContainer();
491+
$session = $container->get('session');
492+
self::assertInstanceOf(Session::class, $session);
493+
$token = $this->createValidCsrfToken($session, 'api-key-update');
494+
$controller->setContainer($container);
495+
496+
$response = $controller->update(new Request([], [], ['id' => 1], [], [], [], json_encode([
497+
'csrf' => $token,
498+
'name' => 'Updated key',
499+
'expiresAt' => 'not-a-date',
500+
], JSON_THROW_ON_ERROR)));
501+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
502+
503+
self::assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode());
504+
self::assertSame('Invalid expiresAt value.', $payload['error']);
505+
}
506+
507+
/**
508+
* @throws \Exception
509+
*/
510+
public function testUpdateReturnsInternalServerErrorWhenUpdateFails(): void
511+
{
512+
$db = $this->createMock(DatabaseDriver::class);
513+
$db->method('escape')->willReturnCallback(static fn(string $value): string => $value);
514+
$db->method('query')->willReturnMap([
515+
[$this->stringContains('SELECT id FROM faqapi_keys'), 0, 0, new \stdClass()],
516+
[$this->stringContains('UPDATE faqapi_keys'), 0, 0, false],
517+
]);
518+
$db->method('numRows')->willReturn(1);
519+
$db->method('error')->willReturn('update failed');
520+
521+
$controller = $this->createController();
522+
$container = $this->createAuthenticatedContainerWithDb($db);
523+
$session = $container->get('session');
524+
self::assertInstanceOf(Session::class, $session);
525+
$token = $this->createValidCsrfToken($session, 'api-key-update');
526+
$controller->setContainer($container);
527+
528+
$response = $controller->update(new Request([], [], ['id' => 1], [], [], [], json_encode([
529+
'csrf' => $token,
530+
'name' => 'Updated key',
531+
'scopes' => ['faq.read'],
532+
], JSON_THROW_ON_ERROR)));
533+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
534+
535+
self::assertSame(Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode());
536+
self::assertSame('update failed', $payload['error']);
537+
}
538+
389539
/**
390540
* @throws \Exception
391541
*/
@@ -403,6 +553,31 @@ public function testDeleteReturnsUnauthorizedForInvalidCsrfWhenAuthenticated():
403553
self::assertSame(Translation::get('msgNoPermission'), $payload['error']);
404554
}
405555

556+
/**
557+
* @throws \Exception
558+
*/
559+
public function testDeleteReturnsInternalServerErrorWhenDeleteFails(): void
560+
{
561+
$db = $this->createMock(DatabaseDriver::class);
562+
$db->method('query')->willReturn(false);
563+
$db->method('error')->willReturn('delete failed');
564+
565+
$controller = $this->createController();
566+
$container = $this->createAuthenticatedContainerWithDb($db);
567+
$session = $container->get('session');
568+
self::assertInstanceOf(Session::class, $session);
569+
$token = $this->createValidCsrfToken($session, 'api-key-delete');
570+
$controller->setContainer($container);
571+
572+
$response = $controller->delete(new Request([], [], ['id' => 1], [], [], [], json_encode([
573+
'csrf' => $token,
574+
], JSON_THROW_ON_ERROR)));
575+
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
576+
577+
self::assertSame(Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode());
578+
self::assertSame('delete failed', $payload['error']);
579+
}
580+
406581
private function seedApiKeyRow(): void
407582
{
408583
$this->dbHandle->query('DELETE FROM faqapi_keys');
@@ -425,6 +600,11 @@ private function createValidCsrfToken(Session $session, string $page): string
425600
}
426601

427602
private function createAuthenticatedContainer(): ContainerInterface
603+
{
604+
return $this->createAuthenticatedContainerWithDb($this->configuration->getDb());
605+
}
606+
607+
private function createAuthenticatedContainerWithDb(DatabaseDriver $db): ContainerInterface
428608
{
429609
$permission = $this->createMock(PermissionInterface::class);
430610
$permission
@@ -443,13 +623,17 @@ private function createAuthenticatedContainer(): ContainerInterface
443623

444624
$session = new Session(new MockArraySessionStorage());
445625
$adminLog = $this->createStub(AdminLog::class);
626+
$configuration = $this->createMock(Configuration::class);
627+
$configuration->method('getDb')->willReturn($db);
628+
$configuration->method('get')->willReturn(false);
629+
$configuration->method('getTemplateSet')->willReturn('default');
446630

447631
$container = $this->createStub(ContainerInterface::class);
448632
$container
449633
->method('get')
450-
->willReturnCallback(function (string $id) use ($currentUser, $session, $adminLog) {
634+
->willReturnCallback(function (string $id) use ($currentUser, $session, $adminLog, $configuration) {
451635
return match ($id) {
452-
'phpmyfaq.configuration' => $this->configuration,
636+
'phpmyfaq.configuration' => $configuration,
453637
'phpmyfaq.user.current_user' => $currentUser,
454638
'session' => $session,
455639
'phpmyfaq.admin.admin-log' => $adminLog,

tests/phpMyFAQ/Controller/Api/CommentControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
use Exception;
88
use phpMyFAQ\Comments;
99
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\Attributes\UsesNamespace;
1012
use PHPUnit\Framework\TestCase;
1113
use Symfony\Component\HttpFoundation\JsonResponse;
1214
use Symfony\Component\HttpFoundation\Request;
1315
use Symfony\Component\HttpFoundation\Response;
1416

1517
#[AllowMockObjectsWithoutExpectations]
18+
#[CoversClass(CommentController::class)]
19+
#[UsesNamespace('phpMyFAQ')]
1620
class CommentControllerTest extends TestCase
1721
{
1822
/**

tests/phpMyFAQ/Controller/Api/FaqControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
use phpMyFAQ\Tags;
1717
use phpMyFAQ\Translation;
1818
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
19+
use PHPUnit\Framework\Attributes\CoversClass;
20+
use PHPUnit\Framework\Attributes\UsesNamespace;
1921
use PHPUnit\Framework\TestCase;
2022
use Symfony\Component\HttpFoundation\JsonResponse;
2123
use Symfony\Component\HttpFoundation\Request;
2224
use Symfony\Component\HttpFoundation\Session\Session;
2325

2426
#[AllowMockObjectsWithoutExpectations]
27+
#[CoversClass(FaqController::class)]
28+
#[UsesNamespace('phpMyFAQ')]
2529
class FaqControllerTest extends TestCase
2630
{
2731
private Configuration $configuration;

tests/phpMyFAQ/Controller/Api/GlossaryControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use phpMyFAQ\Glossary;
88
use phpMyFAQ\Language;
99
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\Attributes\UsesNamespace;
1012
use PHPUnit\Framework\MockObject\Exception;
1113
use PHPUnit\Framework\TestCase;
1214
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -15,6 +17,8 @@
1517
use Symfony\Component\HttpFoundation\Session\Session;
1618

1719
#[AllowMockObjectsWithoutExpectations]
20+
#[CoversClass(GlossaryController::class)]
21+
#[UsesNamespace('phpMyFAQ')]
1822
class GlossaryControllerTest extends TestCase
1923
{
2024
private Configuration $configuration;

tests/phpMyFAQ/Controller/Api/LanguageControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
use phpMyFAQ\Configuration;
66
use phpMyFAQ\Language;
77
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\UsesNamespace;
810
use PHPUnit\Framework\MockObject\Exception;
911
use PHPUnit\Framework\TestCase;
1012
use Symfony\Component\HttpFoundation\JsonResponse;
1113
use Symfony\Component\HttpFoundation\Session\Session;
1214

1315
#[AllowMockObjectsWithoutExpectations]
16+
#[CoversClass(LanguageController::class)]
17+
#[UsesNamespace('phpMyFAQ')]
1418
class LanguageControllerTest extends TestCase
1519
{
1620
/**

tests/phpMyFAQ/Controller/Api/LoginControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111
use phpMyFAQ\Strings;
1212
use phpMyFAQ\Translation;
1313
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\UsesNamespace;
1416
use PHPUnit\Framework\TestCase;
1517
use Symfony\Component\HttpFoundation\JsonResponse;
1618
use Symfony\Component\HttpFoundation\Request;
1719
use Symfony\Component\HttpFoundation\Response;
1820
use Symfony\Component\HttpFoundation\Session\Session;
1921

2022
#[AllowMockObjectsWithoutExpectations]
23+
#[CoversClass(LoginController::class)]
24+
#[UsesNamespace('phpMyFAQ')]
2125
class LoginControllerTest extends TestCase
2226
{
2327
private Configuration $configuration;

tests/phpMyFAQ/Controller/Api/NewsControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use phpMyFAQ\Configuration;
88
use phpMyFAQ\Language;
99
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\Attributes\UsesNamespace;
1012
use PHPUnit\Framework\MockObject\Exception as MockException;
1113
use PHPUnit\Framework\TestCase;
1214
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -15,6 +17,8 @@
1517
use Symfony\Component\HttpFoundation\Session\Session;
1618

1719
#[AllowMockObjectsWithoutExpectations]
20+
#[CoversClass(NewsController::class)]
21+
#[UsesNamespace('phpMyFAQ')]
1822
class NewsControllerTest extends TestCase
1923
{
2024
private Configuration $configuration;

tests/phpMyFAQ/Controller/Api/OAuth2ControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
use phpMyFAQ\Translation;
1616
use phpMyFAQ\User\CurrentUser;
1717
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
18+
use PHPUnit\Framework\Attributes\CoversClass;
19+
use PHPUnit\Framework\Attributes\UsesNamespace;
1820
use PHPUnit\Framework\TestCase;
1921
use Symfony\Component\HttpFoundation\Request;
2022
use Symfony\Component\HttpFoundation\Response;
2123
use Symfony\Component\HttpFoundation\Session\Session;
2224
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
2325

2426
#[AllowMockObjectsWithoutExpectations]
27+
#[CoversClass(OAuth2Controller::class)]
28+
#[UsesNamespace('phpMyFAQ')]
2529
class OAuth2ControllerTest extends TestCase
2630
{
2731
private Configuration $configuration;

tests/phpMyFAQ/Controller/Api/OpenQuestionControllerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
use phpMyFAQ\Database\Sqlite3;
1010
use phpMyFAQ\Question;
1111
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
use PHPUnit\Framework\Attributes\UsesNamespace;
1214
use PHPUnit\Framework\TestCase;
1315
use Symfony\Component\HttpFoundation\JsonResponse;
1416
use Symfony\Component\HttpFoundation\Request;
1517
use Symfony\Component\HttpFoundation\Response;
1618

1719
#[AllowMockObjectsWithoutExpectations]
20+
#[CoversClass(OpenQuestionController::class)]
21+
#[UsesNamespace('phpMyFAQ')]
1822
class OpenQuestionControllerTest extends TestCase
1923
{
2024
private Configuration $configuration;

0 commit comments

Comments
 (0)