160 lines
5.6 KiB
PHP
160 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Enums\PageStatusEnum;
|
|
use App\Livewire\UrlSubmissionForm;
|
|
use App\Models\Page;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use Tests\TestCase;
|
|
|
|
class UrlSubmissionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 1 — route renders the submission form
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_submission_form_renders_at_public_route(): void
|
|
{
|
|
$response = $this->get('/submit');
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertSeeLivewire('url-submission-form');
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 2 — valid submission creates a page row as Discovered
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_valid_url_submission_creates_page_as_discovered(): void
|
|
{
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', 'https://example.com/interesting-post')
|
|
->call('submit')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertDatabaseHas('pages', [
|
|
'url' => 'https://example.com/interesting-post',
|
|
'status' => PageStatusEnum::Discovered,
|
|
'instance_id' => null,
|
|
]);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 3 — duplicate submission is idempotent (no second row created)
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_duplicate_url_submission_does_not_create_second_page(): void
|
|
{
|
|
$url = 'https://example.com/seen-before';
|
|
|
|
Page::factory()->create([
|
|
'url' => $url,
|
|
'status' => PageStatusEnum::Discovered,
|
|
]);
|
|
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', $url)
|
|
->call('submit')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertDatabaseCount('pages', 1);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 4 — confirmation state echoes submitted URL
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_confirmation_state_echoes_submitted_url(): void
|
|
{
|
|
$url = 'https://example.com/great-article';
|
|
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', $url)
|
|
->call('submit')
|
|
->assertHasNoErrors()
|
|
->assertSet('confirmedUrl', $url)
|
|
->assertSet('url', '')
|
|
->assertSee($url);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 5 — empty URL fails validation (regression lock)
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_missing_url_fails_validation(): void
|
|
{
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', '')
|
|
->call('submit')
|
|
->assertHasErrors(['url' => 'required']);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 6 — invalid URL formats fail validation
|
|
// -------------------------------------------------------------------------
|
|
|
|
#[DataProvider('invalidUrls')]
|
|
public function test_invalid_url_formats_fail_validation(string $url): void
|
|
{
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', $url)
|
|
->call('submit')
|
|
->assertHasErrors('url');
|
|
}
|
|
|
|
public static function invalidUrls(): array
|
|
{
|
|
return [
|
|
'no scheme' => ['not-a-url'],
|
|
'disallowed scheme' => ['ftp://example.com'],
|
|
'javascript scheme' => ['javascript:alert(1)'],
|
|
];
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Integration — form submission enqueues a crawl via PageObserver
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_url_submission_form_enqueues_crawl_via_observer(): void
|
|
{
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', 'https://example.com/article')
|
|
->call('submit')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertDatabaseCount('page_crawls', 1);
|
|
$this->assertDatabaseHas('page_crawls', ['domain' => 'example.com']);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test 7 — rate limit blocks the 11th submission within a minute
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function test_rate_limit_blocks_eleventh_submission_within_a_minute(): void
|
|
{
|
|
// 10 submissions within the limit — each must succeed
|
|
for ($i = 1; $i <= 10; $i++) {
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', "https://example.com/post-{$i}")
|
|
->call('submit')
|
|
->assertHasNoErrors();
|
|
}
|
|
|
|
// 11th submission from the same IP must be blocked, with the message visible
|
|
Livewire::test(UrlSubmissionForm::class)
|
|
->set('url', 'https://example.com/post-11')
|
|
->call('submit')
|
|
->assertHasErrors('rate_limit')
|
|
->assertSee('Too many submissions');
|
|
|
|
// The 11th URL must NOT have been persisted
|
|
$this->assertDatabaseCount('pages', 10);
|
|
}
|
|
}
|