2026-05-21 22:27:19 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Enums\Post\Status as PostStatus;
|
|
|
|
|
use App\Models\Post;
|
2026-05-21 22:32:42 +00:00
|
|
|
use App\Support\PostStatusRules;
|
2026-05-21 22:27:19 +00:00
|
|
|
|
|
|
|
|
test('blocks editing for terminal statuses', function (PostStatus $status) {
|
|
|
|
|
$post = Post::factory()->make(['status' => $status]);
|
|
|
|
|
|
2026-05-21 22:32:42 +00:00
|
|
|
expect(PostStatusRules::blocksEditing($post))->toBeTrue();
|
2026-05-21 22:27:19 +00:00
|
|
|
})->with([
|
|
|
|
|
PostStatus::Publishing,
|
|
|
|
|
PostStatus::Published,
|
|
|
|
|
PostStatus::PartiallyPublished,
|
|
|
|
|
PostStatus::Failed,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
test('allows editing for non terminal statuses', function (PostStatus $status) {
|
|
|
|
|
$post = Post::factory()->make(['status' => $status]);
|
|
|
|
|
|
2026-05-21 22:32:42 +00:00
|
|
|
expect(PostStatusRules::blocksEditing($post))->toBeFalse();
|
2026-05-21 22:27:19 +00:00
|
|
|
})->with([
|
|
|
|
|
PostStatus::Draft,
|
|
|
|
|
PostStatus::Scheduled,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
test('blocks deletion for published statuses', function (PostStatus $status) {
|
|
|
|
|
$post = Post::factory()->make(['status' => $status]);
|
|
|
|
|
|
2026-05-21 22:32:42 +00:00
|
|
|
expect(PostStatusRules::blocksDeletion($post))->toBeTrue();
|
2026-05-21 22:27:19 +00:00
|
|
|
})->with([
|
|
|
|
|
PostStatus::Publishing,
|
|
|
|
|
PostStatus::Published,
|
|
|
|
|
PostStatus::PartiallyPublished,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
test('allows deletion for draft, scheduled and failed statuses', function (PostStatus $status) {
|
|
|
|
|
$post = Post::factory()->make(['status' => $status]);
|
|
|
|
|
|
2026-05-21 22:32:42 +00:00
|
|
|
expect(PostStatusRules::blocksDeletion($post))->toBeFalse();
|
2026-05-21 22:27:19 +00:00
|
|
|
})->with([
|
|
|
|
|
PostStatus::Draft,
|
|
|
|
|
PostStatus::Scheduled,
|
|
|
|
|
PostStatus::Failed,
|
|
|
|
|
]);
|
fix: keep post drafts unscheduled by default (#209)
* fix: keep post drafts unscheduled by default
* Align schedule validation and keep drafts unscheduled.
Require scheduled_at only when status is scheduled and the post has no
usable future schedule. Share that rule across web, API, and MCP, keep
create without a date as null, and preserve the legacy date → 09:00 UTC
fallback.
* Polish schedule validation typing and tests.
Type requiresExplicitSchedule status as ?string, reuse a local status
variable in request/tool validation, tighten the web reject assertion,
and collapse overlapping MCP unscheduled-create cases.
* Centralize status helper in post update validation.
Reuse the typed status() helper across FormRequests and the already-parsed
$status in UpdatePostTool so schedule checks stay consistent and less noisy.
* Share scheduled_at update rules across web, API, and MCP.
Centralize schedule validation in PostStatusRules, normalize status parsing
in one place, and align past-schedule coverage across entry points.
* Cover the full unscheduled-draft checklist in Pest.
Add feature coverage for null/past schedule rejection, explicit scheduling,
draft saves, publish-now without a schedule, calendar exclusion, and
09:00 UTC date defaults across web, API, and MCP.
* Remove normalizeStatus helper.
Keep the inline is_string check at the few call sites that read raw
request status before validation — no shared wrapper needed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop is_string status guards from schedule validation.
Accept mixed status in PostStatusRules and rely on strict comparisons
with Rule::requiredIf / Rule::when — malformed input simply does not match.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Paulo Castellano <paulo@castellanos.llc>
2026-08-01 20:39:18 +00:00
|
|
|
|
|
|
|
|
test('requires explicit schedule when status is scheduled and post has no usable schedule', function (?string $scheduledAt, bool $expected) {
|
|
|
|
|
$post = Post::factory()->make([
|
|
|
|
|
'scheduled_at' => $scheduledAt,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
expect(PostStatusRules::requiresExplicitSchedule($post, PostStatus::Scheduled->value))->toBe($expected);
|
|
|
|
|
})->with([
|
|
|
|
|
'missing schedule' => [null, true],
|
|
|
|
|
'past schedule' => [now()->subHour()->toDateTimeString(), true],
|
|
|
|
|
'future schedule' => [now()->addDay()->toDateTimeString(), false],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
test('does not require explicit schedule for non scheduled statuses', function () {
|
|
|
|
|
$post = Post::factory()->make(['scheduled_at' => null]);
|
|
|
|
|
|
|
|
|
|
expect(PostStatusRules::requiresExplicitSchedule($post, PostStatus::Draft->value))->toBeFalse()
|
|
|
|
|
->and(PostStatusRules::requiresExplicitSchedule(null, PostStatus::Scheduled->value))->toBeTrue();
|
|
|
|
|
});
|