diff --git a/LICENCE.txt b/LICENCE.txt
index 6fc5283e..34f77526 100644
--- a/LICENCE.txt
+++ b/LICENCE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2023 Samuel Gfeller
+Copyright (c) 2024 Samuel Gfeller
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
diff --git a/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php b/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php
index 5c7030b9..c8f12337 100644
--- a/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php
+++ b/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php
@@ -50,7 +50,7 @@ public function __invoke(ServerRequest $request, Response $response): Response
} catch (InvalidTokenException $ite) {
$this->templateRenderer->addPhpViewAttribute(
'formErrorMessage',
- __(
+ __( // Message below asserted in PasswordResetSubmitActionTest
'Invalid, used or expired link.
Please request a new link below and make
sure to click on the most recent email we send you.'
)
diff --git a/src/Application/Action/User/Ajax/UserCreateAction.php b/src/Application/Action/User/Ajax/UserCreateAction.php
index 5cc31f9f..38334156 100644
--- a/src/Application/Action/User/Ajax/UserCreateAction.php
+++ b/src/Application/Action/User/Ajax/UserCreateAction.php
@@ -4,7 +4,6 @@
use App\Application\Responder\JsonResponder;
use App\Domain\User\Service\UserCreator;
-use Odan\Session\SessionInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as ServerRequest;
use Psr\Log\LoggerInterface;
@@ -16,7 +15,6 @@ public function __construct(
private LoggerInterface $logger,
private JsonResponder $jsonResponder,
private UserCreator $userCreator,
- private SessionInterface $session,
) {
}
diff --git a/src/Domain/Authentication/Exception/InvalidCredentialsException.php b/src/Domain/Authentication/Exception/InvalidCredentialsException.php
index e9c35e8d..eb0dbe8b 100644
--- a/src/Domain/Authentication/Exception/InvalidCredentialsException.php
+++ b/src/Domain/Authentication/Exception/InvalidCredentialsException.php
@@ -7,6 +7,7 @@ class InvalidCredentialsException extends AuthenticationException
// Voluntarily not more information
private string $userEmail;
+ // Invalid credentials asserted in LoginSubmitActionTest
public function __construct(string $email, string $message = 'Invalid credentials')
{
parent::__construct($message);
diff --git a/src/Infrastructure/Console/SqlSchemaGenerator.php b/src/Infrastructure/Console/SqlSchemaGenerator.php
index b165b36d..5b88edd1 100644
--- a/src/Infrastructure/Console/SqlSchemaGenerator.php
+++ b/src/Infrastructure/Console/SqlSchemaGenerator.php
@@ -69,7 +69,7 @@ private function query(string $sql): PDOStatement
$statement = $this->pdo->query($sql);
if (!$statement) {
- throw new UnexpectedValueException('Query failed: ' . $sql . ' Error: ' . $this->pdo->errorInfo()[2] ?? '');
+ throw new UnexpectedValueException('Query failed: ' . $sql . ' Error: ' . $this->pdo->errorInfo()[2]);
}
return $statement;
diff --git a/tests/Fixture/FixtureInterface.php b/tests/Fixture/FixtureInterface.php
index 0dfe7899..555c1902 100644
--- a/tests/Fixture/FixtureInterface.php
+++ b/tests/Fixture/FixtureInterface.php
@@ -2,6 +2,11 @@
namespace App\Test\Fixture;
+/**
+ * Fixture classes contain the properties $table and $records.
+ * @property string $table
+ * @property array $records
+ */
interface FixtureInterface
{
// Attributes are public but php doesn't support class properties in interfaces so getters are needed
diff --git a/tests/Fixture/NoteFixture.php b/tests/Fixture/NoteFixture.php
index 7cdc8c5a..6ae7dc6c 100644
--- a/tests/Fixture/NoteFixture.php
+++ b/tests/Fixture/NoteFixture.php
@@ -3,8 +3,7 @@
namespace App\Test\Fixture;
/**
- * Post values that can be inserted into the database
- * UserFixture HAS to be inserted first.
+ * Note values that can be inserted into the database
*/
class NoteFixture implements FixtureInterface
{
diff --git a/tests/Integration/Authentication/LoginSubmitActionTest.php b/tests/Integration/Authentication/LoginSubmitActionTest.php
index c638f99c..be3a9c6f 100644
--- a/tests/Integration/Authentication/LoginSubmitActionTest.php
+++ b/tests/Integration/Authentication/LoginSubmitActionTest.php
@@ -96,6 +96,14 @@ public function testLoginSubmitActionWrongPassword(): void
// Assert that session user_id is not set
self::assertNull($this->container->get(SessionInterface::class)->get('user_id'));
+
+ // Get response body as string from stream
+ $stream = $response->getBody();
+ $stream->rewind();
+ $body = $stream->getContents();
+
+ // Assert that response body contains validation error
+ self::assertStringContainsString('Invalid credentials', $body);
}
/**
@@ -104,8 +112,9 @@ public function testLoginSubmitActionWrongPassword(): void
* @dataProvider \App\Test\Provider\Authentication\AuthenticationProvider::invalidLoginCredentialsProvider()
*
* @param array $invalidLoginValues valid credentials
+ * @param string $errorMessage validation message that should be in response body
*/
- public function testLoginSubmitActionInvalidValues(array $invalidLoginValues): void
+ public function testLoginSubmitActionInvalidValues(array $invalidLoginValues, string $errorMessage): void
{
$this->insertFixturesWithAttributes([], new UserFixture());
@@ -119,6 +128,14 @@ public function testLoginSubmitActionInvalidValues(array $invalidLoginValues): v
$session = $this->container->get(SessionInterface::class);
// Assert that session user_id is not set
self::assertNull($session->get('user_id'));
+
+ // Get response body as string from stream
+ $stream = $response->getBody();
+ $stream->rewind();
+ $body = $stream->getContents();
+
+ // Assert that response body contains validation error
+ self::assertStringContainsString($errorMessage, $body);
}
/**
diff --git a/tests/Integration/Authentication/PasswordForgottenEmailSubmitActionTest.php b/tests/Integration/Authentication/PasswordForgottenEmailSubmitActionTest.php
index 7e6b2b1d..0e576cb9 100644
--- a/tests/Integration/Authentication/PasswordForgottenEmailSubmitActionTest.php
+++ b/tests/Integration/Authentication/PasswordForgottenEmailSubmitActionTest.php
@@ -44,10 +44,13 @@ public function testPasswordForgottenEmailSubmit(): void
// Simulate logged-in user with id 2
$this->container->get(SessionInterface::class)->set('user_id', $userId);
- $request = $this->createFormRequest('POST', // Request to change password
- $this->urlFor('password-forgotten-email-submit'), [
+ $request = $this->createFormRequest(
+ 'POST', // Request to change password
+ $this->urlFor('password-forgotten-email-submit'),
+ [
'email' => $email,
- ]);
+ ]
+ );
$response = $this->app->handle($request);
@@ -108,10 +111,13 @@ public function testPasswordForgottenEmailSubmitUserNotExisting(): void
{
// Not inserting user as it shouldn't exist
- $request = $this->createFormRequest('POST', // Request to change password
- $this->urlFor('password-forgotten-email-submit'), [
+ $request = $this->createFormRequest(
+ 'POST', // Request to change password
+ $this->urlFor('password-forgotten-email-submit'),
+ [
'email' => 'user@example.com',
- ]);
+ ]
+ );
$response = $this->app->handle($request);
@@ -135,17 +141,25 @@ public function testPasswordForgottenEmailSubmitInvalidData(): void
// Simulate logged-in user with id 2
$this->container->get(SessionInterface::class)->set('user_id', $userRow['id']);
- $request = $this->createFormRequest('POST', // Request to change password
- $this->urlFor('password-forgotten-email-submit'), [
+ $request = $this->createFormRequest(
+ 'POST', // Request to change password
+ $this->urlFor('password-forgotten-email-submit'),
+ [
'email' => 'inval$d@ema$l.com',
- ]);
+ ]
+ );
$response = $this->app->handle($request);
// Assert that response has error status 422
self::assertSame(StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY, $response->getStatusCode());
- // As form is directly rendered with validation errors it's not possible to test them as response is a stream
- // There is a visual test in insomnia for this, but I couldn't manage to keep the login session
+ // Get response body as string from stream
+ $stream = $response->getBody();
+ $stream->rewind();
+ $body = $stream->getContents();
+
+ // Assert that response body contains validation error
+ self::assertStringContainsString('Invalid email', $body);
}
}
diff --git a/tests/Integration/Authentication/PasswordResetSubmitActionTest.php b/tests/Integration/Authentication/PasswordResetSubmitActionTest.php
index 2314a556..14106415 100644
--- a/tests/Integration/Authentication/PasswordResetSubmitActionTest.php
+++ b/tests/Integration/Authentication/PasswordResetSubmitActionTest.php
@@ -107,17 +107,22 @@ public function testResetPasswordSubmitInvalidToken(
self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode());
// Assert that token had NOT been used (except if already used)
- self::assertSame(
- $verification->usedAt,
- $this->getTableRowById('user_verification', (int)$verification->id, ['used_at'])['used_at']
- );
+ $this->assertTableRowValue($verification->usedAt, 'user_verification', $verification->id, 'used_at');
- // Assert that password was not changed to the new one
+ // Assert that the password was not changed to the new one
$this->assertTableRowValue(UserStatus::Unverified->value, 'user', $userRow['id'], 'status');
// Assert that password was NOT changed
$dbPasswordHash = $this->getTableRowById('user', $userRow['id'])['password_hash'];
self::assertFalse(password_verify($newPassword, $dbPasswordHash));
+
+ // Get response body as string from stream
+ $stream = $response->getBody();
+ $stream->rewind();
+ $body = $stream->getContents();
+
+ // Assert that response body contains validation error
+ self::assertStringContainsString('Invalid, used or expired link', $body);
}
/**
@@ -154,7 +159,14 @@ public function testResetPasswordSubmitInvalidData(
// Assert that response has error status 422
self::assertSame(StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY, $response->getStatusCode());
- // As form is directly rendered with validation errors it's not possible to test them as response is a stream
+
+ // Get response body as string from stream
+ $stream = $response->getBody();
+ $stream->rewind();
+ $body = $stream->getContents();
+
+ // Assert that response body contains validation error
+ self::assertStringContainsString('Minimum length is 3', $body);
}
/**
diff --git a/tests/Integration/Client/ClientReadPageActionTest.php b/tests/Integration/Client/ClientReadPageActionTest.php
index bd06fbe3..11491d5d 100644
--- a/tests/Integration/Client/ClientReadPageActionTest.php
+++ b/tests/Integration/Client/ClientReadPageActionTest.php
@@ -10,8 +10,6 @@
use Fig\Http\Message\StatusCodeInterface;
use Odan\Session\SessionInterface;
use PHPUnit\Framework\TestCase;
-use Psr\Container\ContainerExceptionInterface;
-use Psr\Container\NotFoundExceptionInterface;
use Selective\TestTrait\Traits\DatabaseTestTrait;
use Selective\TestTrait\Traits\HttpTestTrait;
use Selective\TestTrait\Traits\RouteTestTrait;
@@ -32,12 +30,9 @@ class ClientReadPageActionTest extends TestCase
/**
* Normal page action while being authenticated.
*
- * @throws NotFoundExceptionInterface
- * @throws ContainerExceptionInterface
- *
* @return void
*/
- public function testClientReadPageActionAuthorization(): void
+ public function testClientReadPageActionAuthenticated(): void
{
// Insert linked and authenticated user
$userId = $this->insertFixturesWithAttributes([], new UserFixture())['id'];
diff --git a/tests/Provider/Authentication/AuthenticationProvider.php b/tests/Provider/Authentication/AuthenticationProvider.php
index 629c64df..e2ada4dc 100644
--- a/tests/Provider/Authentication/AuthenticationProvider.php
+++ b/tests/Provider/Authentication/AuthenticationProvider.php
@@ -81,26 +81,27 @@ public static function authenticationSecurityCases(): array
public static function invalidLoginCredentialsProvider(): array
{
return [
- [
+ 'Invalid email' => [
[
- // Invalid email
'email' => 'admin@exam$ple.com',
'password' => '12345678',
],
+ 'validationErrorMessage' => 'Invalid email',
],
- [
+ 'Missing email' => [
[
// Missing email
'email' => '',
'password' => '12345678',
],
+ 'validationErrorMessage' => 'Invalid email',
],
- [
+ 'Missing password' => [
[
- // Missing password
'email' => 'admin@example.com',
'password' => '',
],
+ 'validationErrorMessage' => 'Invalid password',
],
];
}
diff --git a/tests/Provider/Note/NoteProvider.php b/tests/Provider/Note/NoteProvider.php
index 9b5295fd..e3f53295 100644
--- a/tests/Provider/Note/NoteProvider.php
+++ b/tests/Provider/Note/NoteProvider.php
@@ -75,7 +75,7 @@ public static function noteListUserAttributesAndExpectedResultProvider(): array
*/
public static function noteListWithFilterProvider(): array
{
- // Users linked to notes to insert (authenticated user not relevant for this test, he is inserted in test case)
+ // Users linked to notes to insert (authenticated user not relevant for this test, inserted in test case)
$usersToInsert = [
['id' => 10],
['id' => 11],
diff --git a/tests/Provider/Security/LoginRequestProvider.php b/tests/Provider/Security/LoginRequestProvider.php
index 0bf373cc..664c146f 100644
--- a/tests/Provider/Security/LoginRequestProvider.php
+++ b/tests/Provider/Security/LoginRequestProvider.php
@@ -4,9 +4,6 @@
class LoginRequestProvider
{
- // ! This must be the same as the mocked config values in the unit test case
- private const userLoginThrottleThresholds = [4 => 10, 9 => 120, 12 => 'captcha'];
-
private const securitySettings = [
'throttle_login' => true,
'login_throttle_rule' => [4 => 10, 9 => 120, 12 => 'captcha'],
diff --git a/tests/Traits/DatabaseExtensionTestTrait.php b/tests/Traits/DatabaseExtensionTestTrait.php
index f3ea5a82..5e28b402 100644
--- a/tests/Traits/DatabaseExtensionTestTrait.php
+++ b/tests/Traits/DatabaseExtensionTestTrait.php
@@ -6,7 +6,7 @@
use Selective\TestTrait\Traits\DatabaseTestTrait;
/**
- * Slim App Route Test Trait.
+ * Extension of DatabaseTestTrait to add more functionality.
*/
trait DatabaseExtensionTestTrait
{
diff --git a/tests/Traits/FixtureTestTrait.php b/tests/Traits/FixtureTestTrait.php
index 05055fcd..e110538d 100644
--- a/tests/Traits/FixtureTestTrait.php
+++ b/tests/Traits/FixtureTestTrait.php
@@ -12,48 +12,6 @@ trait FixtureTestTrait
{
use DatabaseTestTrait;
- /**
- * Takes default record and modifies it to suit given attributes.
- * This makes a lot more sense than storing all different kinds
- * of records in each fixture and then searching them as the test
- * function can control fixtures and there is no dependencies.
- *
- * @param array $attributes
- * @param FixtureInterface $fixture
- * @param int $amount amount rows that will be returned
- *
- * @return array either one row of requested fixture or array of rows if $amount is higher than 1
- */
- protected function getFixtureRecordsWithAttributes(array $attributes, FixtureInterface $fixture, int $amount = 1): array
- {
- $rows = $fixture->getRecords();
- $returnArray = [];
- $rowKey = 0;
- // To have a pool of different data; instead of taking one basic record when multiple records are asked,
- // this iterates over the existing records
- for ($i = 0; $i <= $amount; $i++) {
- // If there are no more rows for the row key, reset it to 0
- if (!isset($rows[$rowKey])) {
- $rowKey = 0;
- }
- // Remove id from row key before setting new values as id might be a given attribute
- unset($rows[$rowKey]['id']);
- // Add given attributes to row
- foreach ($attributes as $colum => $value) {
- // Set value to given attribute value
- $rows[$rowKey][$colum] = $value;
- }
- $returnArray[] = $rows[$rowKey];
- $rowKey++;
- }
-
- if ($amount === 1) {
- return $returnArray[0];
- }
-
- return $returnArray;
- }
-
/**
* Inserts fixtures with given attributes and returns rows with id
* This has to advantage to remove the dependency of each fixtures'
@@ -109,81 +67,47 @@ protected function insertFixturesWithAttributes(
}
/**
- * Insert multiple given fixture rows.
- *
- * @param string $table
- * @param array $rows
- *
- * @return array rows with id
- */
- protected function insertFixtureRows(string $table, array $rows): array
- {
- foreach ($rows as $key => $row) {
- $rows[$key]['id'] = (int)$this->insertFixture($table, $row);
- }
-
- return $rows;
- }
-
- /**
- * Returns fixture rows where given condition matches
- * Note: this relies on the function of selective/test-traits DatabaseTestTrait.php.
+ * Takes the default record and modifies it to suit given attributes.
+ * This makes a lot more sense than storing all different kinds
+ * of records in each fixture and then searching them as the test
+ * function can control fixtures, and there are no dependencies.
*
- * @param array $conditions array of db field name and the expected value. Example:
- * ['field_name' => 'expected_value', 'other_field_name' => 'other expected value',]
+ * @param array $attributes
* @param FixtureInterface $fixture
- * @param array $oppositeConditions optional NOT conditions. If ['id' => 1] is provided -> user 1 will NOT be returned
+ * @param int $amount amount rows that will be returned
*
- * @return array[] records matching the conditions
+ * @return array either one row of requested fixture or array of rows if $amount is higher than 1
*/
- protected function findRecordsFromFixtureWhere(
- array $conditions,
+ private function getFixtureRecordsWithAttributes(
+ array $attributes,
FixtureInterface $fixture,
- array $oppositeConditions = []
+ int $amount = 1
): array {
$rows = $fixture->getRecords();
- $matchingRecords = [];
- // Loop over all records (rows)
- foreach ($rows as $row) {
- // Check if condition matches on row columns
- foreach ($conditions as $columnToCheck => $expectedColumnValue) {
- // If the current condition (in loop) is about the field, check the value
- if ($row[$columnToCheck] !== $expectedColumnValue) {
- // If one value of the row does not match the condition, the rest of the current rows iteration is skipped
- continue 2;
- }
+ $returnArray = [];
+ $rowKey = 0;
+ // To have a pool of different data; instead of taking one basic record when multiple records are asked,
+ // this iterates over the existing records
+ for ($i = 0; $i <= $amount; $i++) {
+ // If there are no more rows for the row key, reset it to 0
+ if (!isset($rows[$rowKey])) {
+ $rowKey = 0;
}
- // Check if opposite condition matches rows that should NOT be returned
- foreach ($oppositeConditions as $columnToCheck => $expectedColumnValue) {
- // If the current opposite condition (in loop) concerns the $columnToCheck, check if the value matches
- if ($row[$columnToCheck] === $expectedColumnValue) {
- // If one value of the row matches the condition, the rest of the current rows iteration is skipped
- continue 2;
- }
+ // Remove id from row key before setting new values as id might be a given attribute
+ unset($rows[$rowKey]['id']);
+ // Add given attributes to row
+ foreach ($attributes as $colum => $value) {
+ // Set value to given attribute value
+ $rows[$rowKey][$colum] = $value;
}
-
- // If all conditions matched, this part is not skipped (with continue) and row is added to matching records
- $matchingRecords[] = $row;
+ $returnArray[] = $rows[$rowKey];
+ $rowKey++;
}
- return $matchingRecords;
- }
-
- /**
- * If only specific fixtures should be inserted for instance
- * linked to a specific resource.
- *
- * @param array $conditions array of db column name and the expected value.
- * Shape: ['field_name' => 'expected_value', 'other_field_name' => 'other expected value',]
- * @param FixtureInterface $fixture
- *
- * @return void
- */
- protected function insertFixtureWhere(array $conditions, FixtureInterface $fixture): void
- {
- $filteredRecords = $this->findRecordsFromFixtureWhere($conditions, $fixture);
- foreach ($filteredRecords as $row) {
- $this->insertFixture($fixture->getTable(), $row);
+ if ($amount === 1) {
+ return $returnArray[0];
}
+
+ return $returnArray;
}
}