jonnybarnes.uk/tests/Unit/ArticlesTest.php
Jonny Barnes eb35a0aa2d
Update existing draft articles instead of erroring on a repeat Micropub post
If a Micropub h-entry post's title matches an existing article that's
still a draft, update that article in place rather than trying to
insert a duplicate. If it matches one that's already published,
reject the request with a clear error instead of silently colliding.

Also set includeTrashed on Article's slug config as a safety net: this
model soft-deletes, and Sluggable's uniqueness check ignores trashed
rows by default, so a previously-deleted article's title could crash
new inserts with a raw unique constraint violation (this is exactly
what surfaced in Flare as a UniqueConstraintViolationException on
articles_titleurl_unique once the prior swallowed-exception fix
shipped).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:16:11 +01:00

122 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Models\Article;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class ArticlesTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function title_slug_is_generated_automatically(): void
{
$article = new Article;
$article->title = 'My Title';
$article->main = 'Content';
$article->save();
$this->assertEquals('my-title', $article->titleurl);
}
#[Test]
public function markdown_content_is_converted(): void
{
$article = new Article;
$article->main = 'Some *markdown*';
$this->assertEquals('<p>Some <em>markdown</em></p>'.PHP_EOL, $article->html);
}
#[Test]
public function we_generate_the_different_time_attributes(): void
{
$article = Article::create([
'title' => 'Test',
'main' => 'test',
]);
$this->assertEquals($article->w3c_time, $article->updated_at->toW3CString());
$this->assertEquals($article->tooltip_time, $article->updated_at->toRFC850String());
$this->assertEquals($article->human_time, $article->updated_at->diffForHumans());
$this->assertEquals($article->pubdate, $article->updated_at->toRSSString());
}
#[Test]
public function we_generate_the_article_link_from_the_slug(): void
{
$article = Article::create([
'title' => 'Test',
'main' => 'Test',
]);
$article->title = 'Test Title';
$this->assertEquals(
'/blog/'.date('Y').'/'.date('m').'/test',
$article->link
);
}
#[Test]
public function uri_is_the_absolute_form_of_the_link(): void
{
$article = Article::create([
'title' => 'Test',
'main' => 'Test',
]);
$this->assertEquals(config('app.url').$article->link, $article->uri);
}
#[Test]
public function slug_is_suffixed_when_a_trashed_article_already_used_it(): void
{
$original = Article::create(['title' => 'My Title', 'main' => 'Content']);
$original->delete();
$newArticle = Article::create(['title' => 'My Title', 'main' => 'Other content']);
$this->assertEquals('my-title-2', $newArticle->titleurl);
}
#[Test]
public function date_scope_returns_expected_articles(): void
{
Article::factory()->create([
'created_at' => Carbon::now()->subYear()->toDateTimeString(),
'updated_at' => Carbon::now()->subYear()->toDateTimeString(),
]);
Article::factory()->create();
$yearAndMonth = Article::date(date('Y'), date('m'))->get();
$this->assertCount(1, $yearAndMonth);
$priorYear = Article::date(date('Y') - 2, 1)->get();
$this->assertCount(0, $priorYear);
$emptyScope = Article::date()->get();
$this->assertCount(2, $emptyScope);
}
#[Test]
public function date_scope_returns_expected_articles_for_december(): void
{
Article::factory()->create([
'created_at' => Carbon::now()->setDay(11)->setMonth(11)->toDateTimeString(),
'updated_at' => Carbon::now()->setDay(11)->setMonth(11)->toDateTimeString(),
]);
Article::factory()->create([
'created_at' => Carbon::now()->setMonth(12)->setDay(12)->toDateTimeString(),
'updated_at' => Carbon::now()->setMonth(12)->setDay(12)->toDateTimeString(),
]);
$this->assertCount(1, Article::date(date('Y'), 12)->get());
}
}