195 lines
6.8 KiB
PHP
195 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Models;
|
|
|
|
use App\Enums\PageStatusEnum;
|
|
use App\Models\Page;
|
|
use App\Models\PageCrawl;
|
|
use App\Models\PageLink;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Lvl0\FediDiscover\Config\InstanceType;
|
|
use Lvl0\FediDiscover\Models\Instance;
|
|
use Tests\TestCase;
|
|
|
|
class PageTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Queue::fake();
|
|
}
|
|
|
|
public function test_page_model_fillable_fields_can_be_mass_assigned(): void
|
|
{
|
|
$page = Page::create([
|
|
'url' => 'https://example.com/article',
|
|
'status' => 'discovered',
|
|
'title' => 'An Example Article',
|
|
'instance_id' => null,
|
|
'posted_at' => null,
|
|
'fetched_at' => null,
|
|
]);
|
|
|
|
$fresh = $page->fresh();
|
|
|
|
$this->assertNotNull($fresh);
|
|
$this->assertSame('https://example.com/article', $fresh->url);
|
|
$this->assertSame('An Example Article', $fresh->title);
|
|
$this->assertNull($fresh->instance_id);
|
|
}
|
|
|
|
public function test_page_instance_relationship_returns_the_owning_instance(): void
|
|
{
|
|
$instance = Instance::factory()
|
|
->type(InstanceType::Mastodon)
|
|
->enabled()
|
|
->create();
|
|
|
|
$page = Page::create([
|
|
'url' => 'https://example.com/post/1',
|
|
'status' => 'discovered',
|
|
'instance_id' => $instance->id,
|
|
]);
|
|
|
|
$fresh = $page->fresh();
|
|
|
|
$this->assertInstanceOf(Instance::class, $fresh->instance);
|
|
$this->assertSame($instance->id, $fresh->instance->id);
|
|
}
|
|
|
|
public function test_page_outgoing_and_incoming_links_relationships(): void
|
|
{
|
|
$source = Page::factory()->create(['url' => 'https://example.com/source']);
|
|
$target = Page::factory()->create(['url' => 'https://example.com/target']);
|
|
|
|
PageLink::create([
|
|
'source_page_id' => $source->id,
|
|
'target_page_id' => $target->id,
|
|
]);
|
|
|
|
$freshSource = $source->fresh();
|
|
$freshTarget = $target->fresh();
|
|
|
|
$this->assertCount(1, $freshSource->outgoingLinks);
|
|
$this->assertCount(0, $freshSource->incomingLinks);
|
|
$this->assertCount(1, $freshTarget->incomingLinks);
|
|
$this->assertCount(0, $freshTarget->outgoingLinks);
|
|
|
|
$this->assertSame($source->id, $freshTarget->incomingLinks->first()->source_page_id);
|
|
$this->assertSame($target->id, $freshSource->outgoingLinks->first()->target_page_id);
|
|
}
|
|
|
|
public function test_page_language_is_fillable_and_persists(): void
|
|
{
|
|
$page = Page::create([
|
|
'url' => 'https://example.com/crawled',
|
|
'status' => 'discovered',
|
|
'language' => 'en',
|
|
]);
|
|
|
|
$fresh = $page->fresh();
|
|
|
|
$this->assertNotNull($fresh);
|
|
$this->assertSame('en', $fresh->language);
|
|
|
|
$unset = Page::create([
|
|
'url' => 'https://example.com/no-language',
|
|
'status' => 'discovered',
|
|
]);
|
|
|
|
$this->assertNull($unset->fresh()->language);
|
|
}
|
|
|
|
public function test_page_has_many_crawls(): void
|
|
{
|
|
// createQuietly() skips the PageObserver so no auto-crawl row is inserted;
|
|
// this test is about HasMany scoping, not observer side effects.
|
|
$page = Page::factory()->createQuietly();
|
|
$other = Page::factory()->createQuietly();
|
|
|
|
PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com']);
|
|
PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com']);
|
|
PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com']);
|
|
PageCrawl::create(['page_id' => $other->id, 'domain' => 'other.com']);
|
|
|
|
$crawls = $page->fresh()->crawls;
|
|
|
|
$this->assertCount(3, $crawls);
|
|
foreach ($crawls as $crawl) {
|
|
$this->assertInstanceOf(PageCrawl::class, $crawl);
|
|
$this->assertSame($page->id, $crawl->page_id);
|
|
}
|
|
}
|
|
|
|
public function test_page_latest_crawl_returns_row_with_latest_created_at(): void
|
|
{
|
|
// createQuietly() skips the PageObserver; this test is about latestOfMany ordering,
|
|
// not observer side effects. Using create() would add an observer crawl whose
|
|
// created_at is now(), making the test fragile once the hardcoded sentinel date passes.
|
|
$page = Page::factory()->createQuietly();
|
|
|
|
$old = PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com']);
|
|
$old->created_at = Carbon::parse('2026-01-01 08:00:00');
|
|
$old->save();
|
|
|
|
$middle = PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com']);
|
|
$middle->created_at = Carbon::parse('2026-03-15 12:00:00');
|
|
$middle->save();
|
|
|
|
$newest = PageCrawl::create(['page_id' => $page->id, 'domain' => 'example.com', 'error_message' => 'sentinel-latest']);
|
|
$newest->created_at = Carbon::parse('2026-05-10 18:00:00');
|
|
$newest->save();
|
|
|
|
$latest = $page->fresh()->latestCrawl;
|
|
|
|
$this->assertInstanceOf(PageCrawl::class, $latest);
|
|
$this->assertSame('sentinel-latest', $latest->error_message);
|
|
}
|
|
|
|
public function test_language_confidence_is_fillable_nullable_and_cast_to_float(): void
|
|
{
|
|
// Column must exist, be nullable (null round-trips cleanly), be mass-assignable,
|
|
// and the 'float' cast must be applied so we get a PHP float back, not a string.
|
|
$withConfidence = Page::factory()->createQuietly([
|
|
'language' => 'en',
|
|
'language_confidence' => 0.857,
|
|
]);
|
|
|
|
$fresh = $withConfidence->fresh();
|
|
|
|
$this->assertNotNull($fresh);
|
|
$this->assertIsFloat($fresh->language_confidence);
|
|
$this->assertEqualsWithDelta(0.857, $fresh->language_confidence, 0.001);
|
|
|
|
$withoutConfidence = Page::factory()->createQuietly();
|
|
|
|
$this->assertNull($withoutConfidence->fresh()->language_confidence);
|
|
}
|
|
|
|
public function test_page_status_is_cast_to_enum(): void
|
|
{
|
|
$cases = [
|
|
['string' => 'discovered', 'enum' => PageStatusEnum::Discovered],
|
|
['string' => 'fetched', 'enum' => PageStatusEnum::Fetched],
|
|
['string' => 'failed', 'enum' => PageStatusEnum::Failed],
|
|
];
|
|
|
|
foreach ($cases as ['string' => $raw, 'enum' => $expected]) {
|
|
$page = Page::create([
|
|
'url' => 'https://example.com/' . $raw,
|
|
'status' => $raw,
|
|
]);
|
|
|
|
$fresh = $page->fresh();
|
|
|
|
$this->assertInstanceOf(PageStatusEnum::class, $fresh->status, "status '{$raw}' should cast to PageStatusEnum");
|
|
$this->assertSame($expected, $fresh->status, "status '{$raw}' should equal PageStatusEnum::{$expected->name}");
|
|
}
|
|
}
|
|
}
|