mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-05-15 16:01:33 +00:00
Added tests for browser submission logic
This commit is contained in:
parent
01886e8ce5
commit
a442c0728a
3 changed files with 514 additions and 0 deletions
247
tests/Controller/BrowserPluginControllerTest.php
Normal file
247
tests/Controller/BrowserPluginControllerTest.php
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Settings\InfoProviderSystem\BrowserPluginSettings;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[Group("slow")]
|
||||
#[Group("DB")]
|
||||
final class BrowserPluginControllerTest extends WebTestCase
|
||||
{
|
||||
// --- GET /browser_info ---
|
||||
|
||||
public function testGetInfoReturns401WhenNotAuthenticated(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/en/tools/info_providers/browser_info');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public function testGetInfoReturnsForbiddenForUnprivilegedUser(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'noread');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('GET', '/en/tools/info_providers/browser_info');
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
public function testGetInfoReturns451WhenPluginDisabled(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
// BrowserPluginSettings::$enabled defaults to false
|
||||
|
||||
$client->request('GET', '/en/tools/info_providers/browser_info');
|
||||
|
||||
self::assertResponseStatusCodeSame(451);
|
||||
}
|
||||
|
||||
public function testGetInfoReturnsJsonWithExpectedKeys(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('GET', '/en/tools/info_providers/browser_info');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertResponseHeaderSame('Content-Type', 'application/json');
|
||||
|
||||
$data = json_decode((string) $client->getResponse()->getContent(), true);
|
||||
$this->assertArrayHasKey('username', $data);
|
||||
$this->assertArrayHasKey('instance_name', $data);
|
||||
$this->assertArrayHasKey('url_providers', $data);
|
||||
$this->assertIsString($data['username']);
|
||||
$this->assertIsString($data['instance_name']);
|
||||
$this->assertIsArray($data['url_providers']);
|
||||
$this->assertNotEmpty($data['username']);
|
||||
$this->assertNotEmpty($data['instance_name']);
|
||||
}
|
||||
|
||||
public function testGetInfoUrlProvidersHaveIdAndLabel(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('GET', '/en/tools/info_providers/browser_info');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$data = json_decode((string) $client->getResponse()->getContent(), true);
|
||||
|
||||
foreach ($data['url_providers'] as $provider) {
|
||||
$this->assertArrayHasKey('id', $provider);
|
||||
$this->assertArrayHasKey('label', $provider);
|
||||
$this->assertIsString($provider['id']);
|
||||
$this->assertIsString($provider['label']);
|
||||
$this->assertNotEmpty($provider['id']);
|
||||
$this->assertNotEmpty($provider['label']);
|
||||
}
|
||||
}
|
||||
|
||||
// --- POST /browser_html ---
|
||||
|
||||
public function testSubmitHtmlReturns401WhenNotAuthenticated(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode(['url' => 'https://example.com', 'html' => '<html/>', 'title' => 'Test']));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlReturns451WhenPluginDisabled(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
// BrowserPluginSettings::$enabled defaults to false
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode(['url' => 'https://example.com', 'html' => '<html/>', 'title' => 'Test']));
|
||||
|
||||
self::assertResponseStatusCodeSame(451);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithValidDataAndProvider(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode([
|
||||
'url' => 'https://example.com/product/123',
|
||||
'html' => '<html><body>Product page</body></html>',
|
||||
'title' => 'Some Product',
|
||||
'provider' => 'generic_web',
|
||||
]));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$data = json_decode((string) $client->getResponse()->getContent(), true);
|
||||
$this->assertArrayHasKey('redirect_url', $data);
|
||||
$this->assertNotNull($data['redirect_url']);
|
||||
$this->assertStringContainsString('generic_web', (string) $data['redirect_url']);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithoutProviderReturnsNullRedirectUrl(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode([
|
||||
'url' => 'https://example.com/product/123',
|
||||
'html' => '<html><body>Product page</body></html>',
|
||||
'title' => 'Some Product',
|
||||
]));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$data = json_decode((string) $client->getResponse()->getContent(), true);
|
||||
$this->assertArrayHasKey('redirect_url', $data);
|
||||
$this->assertNull($data['redirect_url']);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithInvalidJsonReturns400(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], 'this is not valid json {');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithMissingUrlReturns422(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode(['html' => '<html/>', 'title' => 'Test']));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithMissingHtmlReturns422(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode(['url' => 'https://example.com', 'title' => 'Test']));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
public function testSubmitHtmlWithInvalidUrlReturns422(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
$this->loginAsUser($client, 'admin');
|
||||
static::getContainer()->get(BrowserPluginSettings::class)->enabled = true;
|
||||
|
||||
$client->request('POST', '/en/tools/info_providers/browser_html', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode(['url' => 'not-a-url', 'html' => '<html/>', 'title' => 'Test']));
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
private function loginAsUser(mixed $client, string $username): void
|
||||
{
|
||||
$entityManager = static::getContainer()->get('doctrine')->getManager();
|
||||
$user = $entityManager->getRepository(User::class)->findOneBy(['name' => $username]);
|
||||
if (!$user) {
|
||||
$this->markTestSkipped("User '{$username}' not found in fixtures");
|
||||
}
|
||||
$client->loginUser($user);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem\DTOs;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\BrowserSubmittedPage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BrowserSubmittedPageTest extends TestCase
|
||||
{
|
||||
public function testTokenIsNonEmpty(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$this->assertNotEmpty($page->token);
|
||||
}
|
||||
|
||||
public function testTokenIsDeterministic(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com', '<html/>', 'Title A');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com', '<html/>', 'Title B');
|
||||
|
||||
// Token is derived from URL + HTML only, title does not affect it
|
||||
$this->assertSame($page1->token, $page2->token);
|
||||
}
|
||||
|
||||
public function testDifferentUrlProducesDifferentToken(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com/1', '<html/>', 'Test');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com/2', '<html/>', 'Test');
|
||||
|
||||
$this->assertNotSame($page1->token, $page2->token);
|
||||
}
|
||||
|
||||
public function testDifferentHtmlProducesDifferentToken(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com', '<html>A</html>', 'Test');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com', '<html>B</html>', 'Test');
|
||||
|
||||
$this->assertNotSame($page1->token, $page2->token);
|
||||
}
|
||||
|
||||
public function testTokenMatchesPageTokenProperty(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html>content</html>', 'Test');
|
||||
$expected = hash('xxh3', 'https://example.com|<html>content</html>');
|
||||
|
||||
$this->assertSame($expected, $page->token);
|
||||
}
|
||||
|
||||
public function testDefaultSubmittedAtIsNow(): void
|
||||
{
|
||||
$before = new \DateTimeImmutable();
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
$this->assertGreaterThanOrEqual($before->getTimestamp(), $page->submittedAt->getTimestamp());
|
||||
$this->assertLessThanOrEqual($after->getTimestamp(), $page->submittedAt->getTimestamp());
|
||||
}
|
||||
|
||||
public function testCustomSubmittedAt(): void
|
||||
{
|
||||
$dt = new \DateTimeImmutable('2025-01-01 12:00:00');
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test', $dt);
|
||||
|
||||
$this->assertSame($dt, $page->submittedAt);
|
||||
}
|
||||
}
|
||||
181
tests/Services/InfoProviderSystem/SubmittedPageStorageTest.php
Normal file
181
tests/Services/InfoProviderSystem/SubmittedPageStorageTest.php
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Services\InfoProviderSystem;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\BrowserSubmittedPage;
|
||||
use App\Services\InfoProviderSystem\SubmittedPageStorage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
|
||||
final class SubmittedPageStorageTest extends TestCase
|
||||
{
|
||||
private SubmittedPageStorage $storage;
|
||||
private Session $session;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->session = new Session(new MockArraySessionStorage());
|
||||
$request = new Request();
|
||||
$request->setSession($this->session);
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push($request);
|
||||
|
||||
$this->storage = new SubmittedPageStorage($requestStack, new ArrayAdapter());
|
||||
}
|
||||
|
||||
public function testStoreReturnsToken(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$token = $this->storage->store($page);
|
||||
|
||||
$this->assertSame($page->token, $token);
|
||||
}
|
||||
|
||||
public function testStoreAndRetrieve(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html>content</html>', 'Test Page');
|
||||
$token = $this->storage->store($page);
|
||||
|
||||
$retrieved = $this->storage->retrieve($token);
|
||||
|
||||
$this->assertNotNull($retrieved);
|
||||
$this->assertSame($page->url, $retrieved->url);
|
||||
$this->assertSame($page->html, $retrieved->html);
|
||||
$this->assertSame($page->title, $retrieved->title);
|
||||
$this->assertSame($page->token, $retrieved->token);
|
||||
}
|
||||
|
||||
public function testRetrieveReturnsNullForUnknownToken(): void
|
||||
{
|
||||
$this->assertNull($this->storage->retrieve('nonexistent_token_xyz'));
|
||||
}
|
||||
|
||||
public function testStoreReturnsSameTokenForSameUrlAndHtml(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com', '<html/>', 'Title One');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com', '<html/>', 'Title Two');
|
||||
|
||||
$this->assertSame($this->storage->store($page1), $this->storage->store($page2));
|
||||
}
|
||||
|
||||
public function testRemoveByTokenDeletesFromCache(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$token = $this->storage->store($page);
|
||||
|
||||
$this->storage->remove($token);
|
||||
|
||||
$this->assertNull($this->storage->retrieve($token));
|
||||
}
|
||||
|
||||
public function testRemoveByPageObjectDeletesFromCache(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$this->storage->store($page);
|
||||
|
||||
$this->storage->remove($page);
|
||||
|
||||
$this->assertNull($this->storage->retrieve($page->token));
|
||||
}
|
||||
|
||||
public function testRemoveDeletesFromSession(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$this->storage->store($page);
|
||||
|
||||
$this->storage->remove($page);
|
||||
|
||||
$this->assertEmpty($this->storage->getRecentPages());
|
||||
}
|
||||
|
||||
public function testGetRecentPagesReturnsStoredPages(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com/1', '<html>1</html>', 'Page 1');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com/2', '<html>2</html>', 'Page 2');
|
||||
$this->storage->store($page1);
|
||||
$this->storage->store($page2);
|
||||
|
||||
$recent = $this->storage->getRecentPages();
|
||||
|
||||
$this->assertCount(2, $recent);
|
||||
}
|
||||
|
||||
public function testGetRecentPagesReturnsNewestFirst(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com/1', '<html>1</html>', 'Page 1');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com/2', '<html>2</html>', 'Page 2');
|
||||
$this->storage->store($page1);
|
||||
$this->storage->store($page2);
|
||||
|
||||
$recent = $this->storage->getRecentPages();
|
||||
|
||||
$this->assertSame($page2->url, $recent[0]->url);
|
||||
$this->assertSame($page1->url, $recent[1]->url);
|
||||
}
|
||||
|
||||
public function testStoreDeduplicatesSamePageInSession(): void
|
||||
{
|
||||
$page = new BrowserSubmittedPage('https://example.com', '<html/>', 'Test');
|
||||
$this->storage->store($page);
|
||||
$this->storage->store($page);
|
||||
|
||||
$this->assertCount(1, $this->storage->getRecentPages());
|
||||
}
|
||||
|
||||
public function testStoreMovesResubmittedPageToTop(): void
|
||||
{
|
||||
$page1 = new BrowserSubmittedPage('https://example.com/1', '<html>1</html>', 'Page 1');
|
||||
$page2 = new BrowserSubmittedPage('https://example.com/2', '<html>2</html>', 'Page 2');
|
||||
$this->storage->store($page1);
|
||||
$this->storage->store($page2);
|
||||
// Resubmit page1 — it should move back to the top
|
||||
$this->storage->store($page1);
|
||||
|
||||
$recent = $this->storage->getRecentPages();
|
||||
|
||||
$this->assertSame($page1->url, $recent[0]->url);
|
||||
$this->assertSame($page2->url, $recent[1]->url);
|
||||
}
|
||||
|
||||
public function testGetRecentPagesSilentlyOmitsExpiredEntries(): void
|
||||
{
|
||||
// Put a token in the session that has no corresponding cache entry (simulates expiry)
|
||||
$this->session->set('browser_plugin_recent_urls', ['expired_token_xyz']);
|
||||
|
||||
$this->assertEmpty($this->storage->getRecentPages());
|
||||
}
|
||||
|
||||
public function testSessionCappedAtTenEntries(): void
|
||||
{
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$page = new BrowserSubmittedPage("https://example.com/{$i}", "<html>{$i}</html>", "Page {$i}");
|
||||
$this->storage->store($page);
|
||||
}
|
||||
|
||||
$this->assertCount(10, $this->storage->getRecentPages());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue