2026-06-16 20:39:07 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Enums\Post\Status as PostStatus;
|
|
|
|
|
use App\Enums\PostPlatform\ContentType;
|
2026-06-16 20:44:27 +00:00
|
|
|
use App\Enums\SocialAccount\Platform;
|
2026-06-16 20:39:07 +00:00
|
|
|
use App\Enums\UserWorkspace\Role;
|
|
|
|
|
use App\Jobs\PublishPost;
|
|
|
|
|
use App\Mcp\Servers\TryPostServer;
|
2026-06-24 20:08:48 +00:00
|
|
|
use App\Mcp\Tools\Post\AttachMediaFromUploadTool;
|
2026-06-16 20:39:07 +00:00
|
|
|
use App\Mcp\Tools\Post\CreatePostTool;
|
|
|
|
|
use App\Mcp\Tools\Post\PublishPostTool;
|
2026-06-16 20:44:27 +00:00
|
|
|
use App\Mcp\Tools\Post\UpdatePostTool;
|
2026-06-16 20:39:07 +00:00
|
|
|
use App\Models\Post;
|
|
|
|
|
use App\Models\PostPlatform;
|
|
|
|
|
use App\Models\SocialAccount;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Models\Workspace;
|
|
|
|
|
use Illuminate\Support\Facades\Queue;
|
2026-06-24 20:08:48 +00:00
|
|
|
use Illuminate\Support\Str;
|
2026-06-16 20:39:07 +00:00
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
|
$this->user = User::factory()->create();
|
|
|
|
|
$this->workspace = Workspace::factory()->create(['user_id' => $this->user->id]);
|
|
|
|
|
$this->workspace->members()->attach($this->user->id, ['role' => Role::Member->value]);
|
|
|
|
|
$this->user->update(['current_workspace_id' => $this->workspace->id]);
|
|
|
|
|
|
|
|
|
|
$this->discordAccount = SocialAccount::factory()->discord()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'platform_user_id' => '111222333',
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('create post persists Discord channel + embeds meta', function () {
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(CreatePostTool::class, [
|
|
|
|
|
'content' => 'Hello Discord',
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'social_account_id' => $this->discordAccount->id,
|
|
|
|
|
'content_type' => ContentType::DiscordMessage->value,
|
|
|
|
|
'meta' => [
|
|
|
|
|
'channel_id' => '444555666',
|
|
|
|
|
'embeds' => [['title' => 'Release']],
|
|
|
|
|
],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
$meta = PostPlatform::where('social_account_id', $this->discordAccount->id)->sole()->meta;
|
|
|
|
|
|
|
|
|
|
expect($meta['channel_id'])->toBe('444555666')
|
|
|
|
|
->and(data_get($meta, 'embeds.0.title'))->toBe('Release');
|
|
|
|
|
});
|
|
|
|
|
|
feat(linkedin): support PDF document (carousel) posts
Add LinkedIn document posts — the swipeable PDF carousel — for both personal profiles and company pages. This is the format every major competitor exposes via native PDF upload, and the reason a trial user churned.
- New 'document' media type (application/pdf) across the upload pipeline (Type enum, HasMedia, FormRequests incl. chunked, Platform media types)
- New LinkedInDocument / LinkedInPageDocument content types: PDF-only, single-file, with a supportsDocument() flag
- Publisher flow: documents initializeUpload -> PUT -> poll AVAILABLE -> post with content.media.{id,title}; optional document_title meta (falls back to file name)
- PDF is mutually exclusive with image/video, enforced in ContentTypeCompatibleWithMedia
- Frontend: 'Document (PDF)' variant, media rules (100MB cap), composer/gallery/detail PDF cards, real PDF embed in the LinkedIn editor preview, i18n in en/es/pt-BR
- Tests: publishers (personal + page, incl. processing-failure path), enums, compatibility rule, chunked PDF upload, API + MCP document_title round-trip
LinkedIn caps documents at 100MB / 300 pages (Documents API). The page limit is enforced by LinkedIn at publish, not validated client-side.
2026-06-24 19:32:27 +00:00
|
|
|
test('create post persists LinkedIn document_title meta', function () {
|
|
|
|
|
$linkedin = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::LinkedIn]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(CreatePostTool::class, [
|
|
|
|
|
'content' => 'Check our latest deck',
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'social_account_id' => $linkedin->id,
|
2026-06-25 00:05:09 +00:00
|
|
|
'content_type' => ContentType::LinkedInPost->value,
|
feat(linkedin): support PDF document (carousel) posts
Add LinkedIn document posts — the swipeable PDF carousel — for both personal profiles and company pages. This is the format every major competitor exposes via native PDF upload, and the reason a trial user churned.
- New 'document' media type (application/pdf) across the upload pipeline (Type enum, HasMedia, FormRequests incl. chunked, Platform media types)
- New LinkedInDocument / LinkedInPageDocument content types: PDF-only, single-file, with a supportsDocument() flag
- Publisher flow: documents initializeUpload -> PUT -> poll AVAILABLE -> post with content.media.{id,title}; optional document_title meta (falls back to file name)
- PDF is mutually exclusive with image/video, enforced in ContentTypeCompatibleWithMedia
- Frontend: 'Document (PDF)' variant, media rules (100MB cap), composer/gallery/detail PDF cards, real PDF embed in the LinkedIn editor preview, i18n in en/es/pt-BR
- Tests: publishers (personal + page, incl. processing-failure path), enums, compatibility rule, chunked PDF upload, API + MCP document_title round-trip
LinkedIn caps documents at 100MB / 300 pages (Documents API). The page limit is enforced by LinkedIn at publish, not validated client-side.
2026-06-24 19:32:27 +00:00
|
|
|
'meta' => ['document_title' => 'Q2 Report'],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
expect(PostPlatform::where('social_account_id', $linkedin->id)->sole()->meta['document_title'])->toBe('Q2 Report');
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-16 20:44:27 +00:00
|
|
|
test('update post merges per-platform meta', function () {
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
$platform = PostPlatform::factory()->discord()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $this->discordAccount->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'meta' => ['channel_name' => 'general'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(UpdatePostTool::class, [
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'id' => $platform->id,
|
|
|
|
|
'meta' => ['channel_id' => '444555666'],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
$meta = $platform->fresh()->meta;
|
|
|
|
|
expect($meta['channel_id'])->toBe('444555666')
|
|
|
|
|
->and($meta['channel_name'])->toBe('general'); // merged, not overwritten
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('publish guard ignores disabled platforms missing meta', function () {
|
|
|
|
|
Queue::fake();
|
|
|
|
|
|
|
|
|
|
$linkedin = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::LinkedIn]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->linkedin()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $linkedin->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
]);
|
|
|
|
|
// Disabled Discord with no channel must not block the publish.
|
|
|
|
|
PostPlatform::factory()->discord()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $this->discordAccount->id,
|
|
|
|
|
'enabled' => false,
|
|
|
|
|
'meta' => [],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
Queue::assertPushed(PublishPost::class);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-16 20:39:07 +00:00
|
|
|
test('publish post rejects a Discord platform without a channel', function () {
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->discord()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $this->discordAccount->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'meta' => [],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$response->assertHasErrors([__('posts.form.discord.channel_required')]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-16 21:11:29 +00:00
|
|
|
test('publish guard enforces required meta for TikTok and Pinterest', function (string $factoryState, string $field, string $messageKey) {
|
|
|
|
|
$account = SocialAccount::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'platform' => $factoryState === 'tiktok' ? Platform::TikTok : Platform::Pinterest,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->{$factoryState}()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $account->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'meta' => [],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$response->assertHasErrors([__($messageKey)]);
|
|
|
|
|
})->with([
|
|
|
|
|
'tiktok' => ['tiktok', 'privacy_level', 'posts.form.tiktok.privacy_required'],
|
|
|
|
|
'pinterest' => ['pinterest', 'board_id', 'posts.form.pinterest.board_required'],
|
|
|
|
|
]);
|
|
|
|
|
|
2026-06-24 20:08:48 +00:00
|
|
|
test('attach media from upload accepts a PDF for a LinkedIn post', function () {
|
|
|
|
|
$linkedin = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::LinkedIn]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->create([
|
|
|
|
|
'post_id' => $post->id, 'social_account_id' => $linkedin->id,
|
2026-06-25 00:05:09 +00:00
|
|
|
'platform' => Platform::LinkedIn, 'content_type' => ContentType::LinkedInPost, 'enabled' => true,
|
2026-06-24 20:08:48 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$uploadToken = (string) Str::uuid();
|
|
|
|
|
$this->workspace->media()->create([
|
|
|
|
|
'group_id' => (string) Str::uuid(),
|
|
|
|
|
'collection' => 'assets',
|
|
|
|
|
'type' => 'document',
|
|
|
|
|
'path' => 'medias/deck.pdf',
|
|
|
|
|
'original_filename' => 'deck.pdf',
|
|
|
|
|
'mime_type' => 'application/pdf',
|
|
|
|
|
'size' => 1000,
|
|
|
|
|
'order' => 0,
|
|
|
|
|
'upload_token' => $uploadToken,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(AttachMediaFromUploadTool::class, [
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'upload_token' => $uploadToken,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
$media = $post->fresh()->media;
|
|
|
|
|
expect($media)->toHaveCount(1)
|
|
|
|
|
->and($media[0]['type'])->toBe('document')
|
|
|
|
|
->and($media[0]['mime_type'])->toBe('application/pdf');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('publish post succeeds for a LinkedIn document that has a PDF', function () {
|
|
|
|
|
Queue::fake();
|
|
|
|
|
|
|
|
|
|
$linkedin = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::LinkedIn]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
'media' => [[
|
|
|
|
|
'id' => 'doc-1', 'path' => 'medias/deck.pdf', 'url' => 'https://example.com/deck.pdf',
|
|
|
|
|
'type' => 'document', 'mime_type' => 'application/pdf', 'original_filename' => 'deck.pdf',
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->create([
|
|
|
|
|
'post_id' => $post->id, 'social_account_id' => $linkedin->id,
|
2026-06-25 00:05:09 +00:00
|
|
|
'platform' => Platform::LinkedIn, 'content_type' => ContentType::LinkedInPost, 'enabled' => true,
|
2026-06-24 20:08:48 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
Queue::assertPushed(PublishPost::class);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-25 00:05:09 +00:00
|
|
|
test('publish post rejects a LinkedIn post that mixes a PDF with an image', function () {
|
2026-06-24 20:08:48 +00:00
|
|
|
$linkedin = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::LinkedIn]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
2026-06-25 00:05:09 +00:00
|
|
|
'media' => [
|
|
|
|
|
['id' => 'doc-1', 'path' => 'medias/deck.pdf', 'url' => 'https://example.com/deck.pdf', 'type' => 'document', 'mime_type' => 'application/pdf', 'original_filename' => 'deck.pdf'],
|
|
|
|
|
['id' => 'img-1', 'path' => 'medias/slide.jpg', 'url' => 'https://example.com/slide.jpg', 'type' => 'image', 'mime_type' => 'image/jpeg', 'original_filename' => 'slide.jpg'],
|
|
|
|
|
],
|
2026-06-24 20:08:48 +00:00
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->create([
|
2026-06-24 20:36:39 +00:00
|
|
|
'post_id' => $post->id, 'social_account_id' => $linkedin->id,
|
|
|
|
|
'platform' => Platform::LinkedIn, 'content_type' => ContentType::LinkedInPost, 'enabled' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
2026-06-25 00:05:09 +00:00
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
2026-06-24 20:36:39 +00:00
|
|
|
|
2026-06-25 00:05:09 +00:00
|
|
|
$response->assertHasErrors(['A PDF document must be the only attachment.']);
|
2026-06-24 20:36:39 +00:00
|
|
|
});
|
|
|
|
|
|
2026-06-16 20:39:07 +00:00
|
|
|
test('publish post succeeds for a Discord platform with a channel', function () {
|
|
|
|
|
Queue::fake();
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->discord()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $this->discordAccount->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'meta' => ['channel_id' => '444555666'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(PublishPostTool::class, ['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
Queue::assertPushed(PublishPost::class);
|
|
|
|
|
});
|
Add optional Pinterest pin title and destination link (#232)
* Add optional Pinterest pin title, description, and link.
Expose title/description/link across web, API, and MCP; seed description from caption into meta on save, and publish description only from meta.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Expand Pinterest title/description/link test coverage.
Cover web draft persistence and validation bounds, API/MCP update merge and seed, and publisher payload fields on video and carousel pins.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Pinterest: description is post content again.
Keep optional title and link in meta/settings only. Remove the separate description textarea, seed logic, and meta.description path so Pinterest follows the shared caption pattern.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor Pinterest meta handling and validation.
- Update CreatePost and UpdatePost actions to filter out null values from meta fields.
- Introduce a new method in PinterestPublisher to resolve board IDs, ensuring required fields are validated.
- Enhance PinterestSettings component to manage title and link inputs, including validation for HTTP URLs.
- Update PostPlatformMetaRules to enforce URL validation for Pinterest links.
- Add tests for clearing Pinterest title and link, and for rejecting invalid links during scheduling.
This refactor improves the handling of Pinterest metadata and enhances user experience by ensuring proper validation and error handling.
* Add validation messages and attributes for Pinterest meta fields
- Introduced custom validation messages and friendly attribute names for Pinterest link and title fields in PostPlatformMetaRules.
- Updated StorePostRequest, UpdatePostRequest, and related tools to utilize these new messages and attributes.
- Enhanced tests to assert correct error messages for invalid Pinterest links and title length constraints.
This update improves user feedback during post creation and editing, ensuring clarity in validation errors.
* Remove click.prevent directive from Pinterest link in PinterestPreview component.
This change simplifies the link behavior, allowing default click actions to occur, which may enhance user interaction with the Pinterest link.
* Update validation error messages for Pinterest meta fields in tests
- Refined the assertions in PostApiPlatformMetaTest to include localized validation messages for Pinterest title and link fields.
- Ensured that error messages reflect the updated validation rules, enhancing clarity for users during post creation and editing.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 21:15:50 +00:00
|
|
|
|
|
|
|
|
test('create post persists Pinterest title and link meta', function () {
|
|
|
|
|
$pinterest = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::Pinterest]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(CreatePostTool::class, [
|
|
|
|
|
'content' => 'Shared caption',
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'social_account_id' => $pinterest->id,
|
|
|
|
|
'content_type' => ContentType::PinterestPin->value,
|
|
|
|
|
'meta' => [
|
|
|
|
|
'board_id' => 'board-1',
|
|
|
|
|
'title' => 'Pin Title',
|
|
|
|
|
'link' => 'https://example.com/product',
|
|
|
|
|
],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
$meta = PostPlatform::where('social_account_id', $pinterest->id)->sole()->meta;
|
|
|
|
|
|
|
|
|
|
expect(data_get($meta, 'title'))->toBe('Pin Title')
|
|
|
|
|
->and(data_get($meta, 'link'))->toBe('https://example.com/product')
|
|
|
|
|
->and(array_key_exists('description', $meta))->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('update post merges Pinterest title and link meta', function () {
|
|
|
|
|
$pinterest = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::Pinterest]);
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $this->workspace->id,
|
|
|
|
|
'user_id' => $this->user->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
|
|
|
|
'content' => 'Shared caption',
|
|
|
|
|
]);
|
|
|
|
|
$platform = PostPlatform::factory()->pinterest()->create([
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'social_account_id' => $pinterest->id,
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'meta' => ['board_id' => 'board-1'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(UpdatePostTool::class, [
|
|
|
|
|
'post_id' => $post->id,
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'id' => $platform->id,
|
|
|
|
|
'meta' => [
|
|
|
|
|
'board_id' => 'board-1',
|
|
|
|
|
'title' => 'Updated Title',
|
|
|
|
|
'link' => 'https://example.com/updated',
|
|
|
|
|
],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertOk();
|
|
|
|
|
|
|
|
|
|
$meta = $platform->fresh()->meta;
|
|
|
|
|
|
|
|
|
|
expect(data_get($meta, 'title'))->toBe('Updated Title')
|
|
|
|
|
->and(data_get($meta, 'link'))->toBe('https://example.com/updated')
|
|
|
|
|
->and(data_get($meta, 'board_id'))->toBe('board-1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('create post rejects invalid Pinterest destination link', function () {
|
|
|
|
|
$pinterest = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id, 'platform' => Platform::Pinterest]);
|
|
|
|
|
|
|
|
|
|
$response = TryPostServer::actingAs($this->user)
|
|
|
|
|
->tool(CreatePostTool::class, [
|
|
|
|
|
'content' => 'Shared caption',
|
|
|
|
|
'platforms' => [[
|
|
|
|
|
'social_account_id' => $pinterest->id,
|
|
|
|
|
'content_type' => ContentType::PinterestPin->value,
|
|
|
|
|
'meta' => [
|
|
|
|
|
'board_id' => 'board-1',
|
|
|
|
|
'link' => 'ftp://files.example.com/pin',
|
|
|
|
|
],
|
|
|
|
|
]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertHasErrors();
|
|
|
|
|
});
|