trypost/tests/Unit
Paulo Castellano 6496588bbc
Defuse links in X posts to avoid the link-post fee (#308)
X bills a post containing a URL at a much higher rate than a plain post, and
its algorithm demotes link posts. The X version of a post now rewrites every
URL non-clickable (https://example.com/post becomes example(.)com/post):
scheme and www. dropped, every dot of the host replaced with (.).

Leaving a single dot intact would still leave a resolvable domain for X to
detect, so all of them are broken. A scheme or www. proves a token is a URL on
its own; a bare host only counts when its last label is a delegated TLD, which
is the one thing telling acme.com apart from Node.js. That check runs against
App\Support\LinkTlds, generated from the whole IANA root zone in every form a
TLD can appear in a post -- ASCII, punycode and the Unicode it decodes to --
because whatever X links is what X bills, so a hand-picked subset would leave
us paying for its gaps. If the regex engine bails out on pathological input the
original content is returned instead of crashing the publisher.

The transform lives in the Platform::X arm of ContentSanitizer, so it reaches
publishing and the app/API/MCP previews from one place and cannot touch any
other network. Off by default; opt in with X_DEFUSE_LINKS.

The editor counts characters and renders its preview client-side and cannot ask
the server on every keystroke, so the rewrite is mirrored in TypeScript. PHP
stays the source of truth: a parity test fails if the two TLD sets drift, and a
browser test drives the real editor so the mirror is covered rather than
assumed. Without it the composer promised text the network never receives.

Character limits now measure the text a reader will see: sanitized, then with
markup resolved away. Measuring the raw draft blocked saving posts that publish
fine and let through posts the network rejects, and counted the editor's HTML
toward the limit. Measuring the sanitized form alone would have counted
Telegram's escaped entities, rejecting messages Telegram accepts.

Empty content is handled once inside the sanitizer instead of by a guard
repeated at every call site.
2026-08-29 15:31:05 -03:00
..
Actions Make Stripe Checkout configurable via billing env knobs (#252) 2026-08-07 21:25:32 -03:00
Ai/Agents Remove the post-templates feature (#296) 2026-08-19 09:34:26 -03:00
Automation test(automations): extract inline feed XML into shared fixtures 2026-06-16 11:06:46 -03:00
Broadcasting feat: workspace-scoped broadcast channel and post lifecycle events 2026-05-07 16:02:22 -03:00
DataTransferObjects test(tiktok): cover MediaItem dimensions and platform max-width helpers 2026-06-26 12:31:46 -03:00
Enums Add social connect step to welcome before Stripe (#293) 2026-08-17 19:37:43 -03:00
Events Activation checklist + MCP OAuth authorize UX (#239) (#250) 2026-08-07 20:34:43 -03:00
Exceptions/Social Resume in-flight Instagram and TikTok publishes without duplicates (#281) 2026-08-16 15:34:53 -03:00
Mail feat: proactive connection check for at-risk posts + SocialAccount name centralization (#256) 2026-08-09 11:10:39 -03:00
Models feat: fire signup/checkout PostHog events from the backend (#277) 2026-08-12 11:47:47 -03:00
Observers Activation checklist + MCP OAuth authorize UX (#239) (#250) 2026-08-07 20:34:43 -03:00
Pinterest fix: Pinterest video processing timeout — longer poll + retry (#246) 2026-08-06 20:49:37 -03:00
Policies Allow account owners to delete workspaces (#208) 2026-08-01 13:00:38 -04:00
Rules Defuse links in X posts to avoid the link-post fee (#308) 2026-08-29 15:31:05 -03:00
Services Remove the post-templates feature (#296) 2026-08-19 09:34:26 -03:00
Settings Add Ukrainian as a supported platform language (#219) 2026-08-05 19:45:30 -03:00
Social/Meta Connect the Pages a login only reaches through a Business Portfolio (#301) 2026-08-26 10:42:59 -03:00
Socialite refactor: settings redesign, Spanish translations, language system, strict_types 2026-03-30 00:20:43 -03:00
Support Resume in-flight Instagram and TikTok publishes without duplicates (#281) 2026-08-16 15:34:53 -03:00
TikTok Resume in-flight Instagram and TikTok publishes without duplicates (#281) 2026-08-16 15:34:53 -03:00
Traits fix: sanitize invalid UTF-8 bytes in uploaded filenames (#265) 2026-08-09 14:29:29 -03:00
AuthorizesMcpToolTest.php MCP: workspace settings, viewer read access, and token access (#241) 2026-08-06 09:54:51 -03:00
ExampleTest.php refactor: settings redesign, Spanish translations, language system, strict_types 2026-03-30 00:20:43 -03:00
HelpersTest.php fix(security): make SSRF private-network block configurable and guard the last user-URL fetches 2026-07-17 15:40:15 -03:00
MediaItemAltTextTest.php Extract alt-text truncation onto MediaItem and close test gaps 2026-07-11 10:34:57 -03:00
MediaPolymorphTest.php fix: persist morph map alias on AI-generated Media rows 2026-05-04 14:14:14 -03:00
MorphMapTest.php Register all models in the morph map + lock it with a test 2026-06-14 16:12:49 -03:00
PlatformAltTextTest.php Harden per-image alt text across publishers, validation, and attach paths 2026-07-16 13:55:33 -03:00
PostHogServiceTest.php feat: fire signup/checkout PostHog events from the backend (#277) 2026-08-12 11:47:47 -03:00
PostPlatformMetaRulesTest.php Add optional Pinterest pin title and destination link (#232) 2026-08-05 18:15:50 -03:00
PostPlatformTest.php feat: proactive connection check for at-risk posts + SocialAccount name centralization (#256) 2026-08-09 11:10:39 -03:00
PostsErrorsTranslationTest.php fix: Pinterest video processing timeout — longer poll + retry (#246) 2026-08-06 20:49:37 -03:00
RevokeAccessTokensTest.php Make the test suite pass on MySQL (#307) 2026-08-29 11:05:33 -03:00
TryPostServerTest.php MCP: workspace settings, viewer read access, and token access (#241) 2026-08-06 09:54:51 -03:00