trypost/tests/Unit/Rules/ContentFitsPlatformLimitsTest.php
Paulo Castellano 953be22b5b fix(posts): block scheduling when content exceeds any platform's char limit
Threads posts over 500 chars were saved + scheduled successfully and only
failed inside the publish job. The frontend already showed the 537|500 badge
but `canSchedule` ignored content length, so Schedule and Post Now stayed
enabled. Backend `UpdatePostRequest` only capped at 63206 (Facebook's max),
not per-platform.

- Add `Platform::contentOverflow()` as the single source of truth and reuse it
  from `HasSocialHttpClient::validateContentLength` (publish-time).
- New `ContentFitsPlatformLimits` rule applied to the `content` field on
  `App\\UpdatePostRequest`, `Api\\UpdatePostRequest`, and `Api\\StorePostRequest`
  via `Rule::when(...)` so drafts are not blocked.
- Rule dedupes per platform (two Threads accounts -> one error) and reports
  the platform label, hard cap, and overage via i18n.
- Edit.vue feeds `contentLengthOverflows` into `canSchedule` and lists each
  offending platform in `postActionTooltip` using the existing
  `getPlatformLabel` resolver.
2026-05-11 19:39:41 -03:00

62 lines
2 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\SocialAccount\Platform;
use App\Rules\ContentFitsPlatformLimits;
function runFitsRule(string $content, array $platforms): array
{
$errors = [];
$rule = new ContentFitsPlatformLimits(collect($platforms));
$rule->validate('content', $content, function (string $message) use (&$errors): void {
$errors[] = $message;
});
return $errors;
}
test('passes when content fits every platform cap', function () {
$errors = runFitsRule(str_repeat('a', 280), [Platform::X, Platform::Threads, Platform::Facebook]);
expect($errors)->toBe([]);
});
test('fails with the platform label, limit and overage when content exceeds a single platform', function () {
$errors = runFitsRule(str_repeat('a', 537), [Platform::Threads]);
expect($errors)->toHaveCount(1);
expect($errors[0])
->toContain('Threads')
->toContain('500')
->toContain('37');
});
test('emits one error per overflowing platform in a multi-platform set', function () {
// 320 chars: fine for Threads (500), over for X (280) and Bluesky (300).
$errors = runFitsRule(str_repeat('a', 320), [Platform::X, Platform::Bluesky, Platform::Threads]);
expect($errors)->toHaveCount(2);
expect($errors[0])->toContain('X');
expect($errors[1])->toContain('Bluesky');
});
test('deduplicates errors when the same platform appears twice in the collection', function () {
// Two Threads accounts selected, content 600 chars — should still produce ONE error.
$errors = runFitsRule(str_repeat('a', 600), [Platform::Threads, Platform::Threads]);
expect($errors)->toHaveCount(1);
});
test('passes for an empty platforms collection', function () {
$errors = runFitsRule(str_repeat('a', 10_000), []);
expect($errors)->toBe([]);
});
test('treats null content as an empty string and passes', function () {
$errors = runFitsRule('', [Platform::Threads, Platform::X]);
expect($errors)->toBe([]);
});