Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions tests/Unit/Domain/Account/Models/AccountHistoryViewTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/

namespace SP\Tests\Unit\Domain\Account\Models;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use SP\Domain\Account\Models\AccountHistoryView;
use SP\Tests\Support\UnitaryTestCase;

/**
* AccountHistoryView enriches a plain AccountHistory row with the owner's, the group's and the
* editor's names for the history detail screen -- AccountHistory's own columns are already
* covered by AccountHistoryTest, so this covers only the four accessors this subclass adds. A
* getter reading the wrong column here would put one person's name on another's history entry
* without anything failing.
*/
#[Group('unitary')]
class AccountHistoryViewTest extends UnitaryTestCase
{
/**
* The enriched columns, with a distinct value per column so a swapped getter shows up
* immediately.
*
* @return array<string, mixed>
*/
private const ROW = [
'userName' => 'Alice Example',
'userGroupName' => 'Admins',
'userEditName' => 'Bob Example',
'userEditLogin' => 'bob-login',
];

#[Test]
#[DataProvider('accessorProvider')]
public function eachAccessorReadsItsOwnColumn(string $accessor, string $column): void
{
self::assertSame(self::ROW[$column], (new AccountHistoryView(self::ROW))->{$accessor}());
}

/**
* @return array<string, array{string, string}>
*/
public static function accessorProvider(): array
{
$accessors = [
'getUserName' => 'userName',
'getUserGroupName' => 'userGroupName',
'getUserEditName' => 'userEditName',
'getUserEditLogin' => 'userEditLogin',
];

$cases = [];

foreach ($accessors as $accessor => $column) {
$cases[$accessor] = [$accessor, $column];
}

return $cases;
}

/**
* These four columns are nullable like the rest of the row, and a view built from an
* incomplete result (e.g. a history entry whose editor was later deleted) must read as
* nothing rather than raising.
*/
#[Test]
public function anEmptyRowReadsAsNothing(): void
{
$view = new AccountHistoryView();

self::assertNull($view->getUserName());
self::assertNull($view->getUserGroupName());
self::assertNull($view->getUserEditName());
self::assertNull($view->getUserEditLogin());
}
}
46 changes: 46 additions & 0 deletions tests/Unit/Domain/Common/Adapters/SerdeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,41 @@ public function testDeserializeObjectFromJsonIgnoresPropertiesTheClassNoLongerHa
$this->assertSame(42, $out->id);
$this->assertSame('a label', $out->label);
}

/**
* serializeObjectToJson() reflects over every property and hands the lot to json_encode()
* with JSON_THROW_ON_ERROR -- a value json_encode() can never represent (a non-finite float,
* here) must come back as this application's own exception rather than json_decode()'s
* JsonException escaping raw, the same contract every other serialize/deserialize pair here
* keeps. This is what a preset or plugin blob that happened to compute NAN or INF would hit.
*
* @throws SPException
*/
public function testSerializeObjectToJsonWrapsAJsonEncodingFailure()
{
$subject = new SerdeSerializeObjectToJsonTestSubject();
$subject->value = NAN;

$this->expectException(SPException::class);
$this->expectExceptionMessage('Inf and NaN cannot be JSON encoded');

Serde::serializeObjectToJson($subject);
}

/**
* deserializeObjectFromJson() decodes with JSON_THROW_ON_ERROR before it ever reaches for
* reflection, so malformed JSON -- a truncated cache write, a corrupted stored blob -- must
* surface the same way: this application's own exception, not json_decode()'s JsonException.
*
* @throws SPException
*/
public function testDeserializeObjectFromJsonWrapsAJsonDecodingFailure()
{
$this->expectException(SPException::class);
$this->expectExceptionMessage('Syntax error');

Serde::deserializeObjectFromJson('{"id":', SerdeDeserializeObjectFromJsonTestSubject::class);
}
}

/**
Expand All @@ -254,3 +289,14 @@ final class SerdeDeserializeObjectFromJsonTestSubject
public int $id;
public string $label;
}

/**
* A minimal target for Serde::serializeObjectToJson(), holding a value the caller can set to
* something json_encode() refuses (NAN, INF) without depending on a specific domain type.
*
* @see SerdeTest::testSerializeObjectToJsonWrapsAJsonEncodingFailure()
*/
final class SerdeSerializeObjectToJsonTestSubject
{
public float $value = 0.0;
}
55 changes: 55 additions & 0 deletions tests/Unit/Infrastructure/Adapter/In/Web/DataGrid/DataGridTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,61 @@ public function aMissingTemplateIsNotSet()
self::assertNull($grid->getDataActionsTemplate());
}

/**
* setDataHeaderTemplate() has its own try/catch around checkTemplate(), separate from the
* actions, pager and row template setters -- each catches the same FileNotFoundException
* independently, so each needs its own missing-template case to be exercised. This one
* reports through processException() rather than logger(); either way the setter must not
* let the exception escape, or building a grid with one bad template name would take the
* whole page down instead of just rendering without that section.
*
* @throws Exception
*/
#[Test]
public function aMissingHeaderTemplateIsNotSet()
{
$grid = $this->buildGrid();

$result = $grid->setDataHeaderTemplate('no-such-template');

self::assertSame($grid, $result);
}

/**
* Same degrade-and-continue contract as the header template, for the paginator's own
* template setter -- setDataPagerTemplate() reports the missing template through logger()
* rather than processException(), a second, independent catch site.
*
* @throws Exception
*/
#[Test]
public function aMissingPagerTemplateIsNotSet()
{
$grid = $this->buildGrid();

$result = $grid->setDataPagerTemplate('no-such-template');

self::assertSame($grid, $result);
self::assertNull($grid->getDataPagerTemplate());
}

/**
* Same degrade-and-continue contract again, for the per-row template setter -- a third,
* independent catch site, back to reporting through processException().
*
* @throws Exception
*/
#[Test]
public function aMissingRowTemplateIsNotSet()
{
$grid = $this->buildGrid();

$result = $grid->setDataRowTemplate('no-such-template');

self::assertSame($grid, $result);
self::assertNull($grid->getDataRowTemplate());
}

/**
* A template that does exist is resolved to its full path and kept, so the screen renders
* that section instead of silently skipping it. Covers both branches of the template path
Expand Down
Loading