Fall back to display name when a published-post email has no username (#325)
* Fall back to display name when a published-post email has no username. Facebook Pages often have no vanity username, which left the email as "Facebook Page (@)". Use username, then display name, and omit the empty parentheses when both are missing. * Move the notification account label onto PostPlatform. The fallback belongs on the row that always has a platform, so mail and in-app notify can call one method instead of guarding a missing social account at every call site. * Cover the in-app published/failed notification body for Facebook Pages. The email tests were not enough — SendNotification body is what the in-app inbox shows, and that path also goes through notificationLabel().
This commit is contained in:
parent
0db9e0706d
commit
abe687d0fa
8 changed files with 272 additions and 3 deletions
|
|
@ -426,7 +426,7 @@ private function notify(Post $post, PostPlatformStatus $status): void
|
|||
->enabled()
|
||||
->where('status', $status)
|
||||
->get()
|
||||
->map(fn ($pp) => $pp->platform->label().' (@'.data_get($pp, 'socialAccount.username', '').')')
|
||||
->map(fn ($pp) => $pp->notificationLabel())
|
||||
->implode(', ');
|
||||
|
||||
SendNotification::dispatch(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public function content(): Content
|
|||
->get()
|
||||
->filter(fn ($pp) => $pp->status === Status::Failed)
|
||||
->map(fn ($pp) => [
|
||||
'name' => $pp->platform->label().' (@'.data_get($pp, 'socialAccount.username', data_get($pp, 'socialAccount.display_name', '')).')',
|
||||
'name' => $pp->notificationLabel(),
|
||||
'error' => $pp->error_message,
|
||||
])
|
||||
->values()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public function content(): Content
|
|||
->get()
|
||||
->filter(fn ($pp) => $pp->status === Status::Published)
|
||||
->map(fn ($pp) => [
|
||||
'name' => $pp->platform->label().' (@'.data_get($pp, 'socialAccount.username', '').')',
|
||||
'name' => $pp->notificationLabel(),
|
||||
'url' => $pp->platform_url,
|
||||
])
|
||||
->values()
|
||||
|
|
|
|||
|
|
@ -89,6 +89,25 @@ public function getDisplayUsernameAttribute(): ?string
|
|||
return $this->socialAccount?->username ?? $this->platform_username;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Facebook Page (@handle)" for emails and in-app notifications.
|
||||
* Username first, then display name (live account or the snapshot
|
||||
* kept on this row). When neither is set — or the account is gone
|
||||
* and there is no snapshot — just the platform name, never "(@)".
|
||||
*/
|
||||
public function notificationLabel(): string
|
||||
{
|
||||
$identifier = $this->display_username
|
||||
?: $this->socialAccount?->display_name
|
||||
?: $this->platform_name;
|
||||
|
||||
if (! filled($identifier) || $identifier === $this->platform->label()) {
|
||||
return $this->platform->label();
|
||||
}
|
||||
|
||||
return "{$this->platform->label()} (@{$identifier})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get avatar URL, falling back to snapshot if account was deleted.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
use App\Models\Workspace;
|
||||
use App\Services\Media\MediaOptimizer;
|
||||
use App\Services\Social\ConnectionVerifier;
|
||||
use App\Services\Social\FacebookPublisher;
|
||||
use App\Services\Social\LinkedInPagePublisher;
|
||||
use App\Services\Social\LinkedInPublisher;
|
||||
use App\Services\Social\PinterestPublisher;
|
||||
|
|
@ -1275,6 +1276,77 @@
|
|||
Queue::assertPushed(SendNotification::class);
|
||||
});
|
||||
|
||||
test('in-app published notification falls back to the facebook page display name', function () {
|
||||
Event::fake();
|
||||
Queue::fake();
|
||||
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$post = Post::factory()->scheduled()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$publisher = Mockery::mock(FacebookPublisher::class);
|
||||
$publisher->shouldReceive('publish')->andReturn([
|
||||
'id' => 'fb-123',
|
||||
'url' => 'https://www.facebook.com/permalink.php?story_fbid=pfbid0&id=61592851040951',
|
||||
]);
|
||||
$this->app->instance(FacebookPublisher::class, $publisher);
|
||||
|
||||
(new PublishToSocialPlatform($postPlatform))->handle();
|
||||
|
||||
Queue::assertPushed(SendNotification::class, function (SendNotification $job) use ($post) {
|
||||
return $job->type === Type::PostPublished
|
||||
&& $job->title === 'Post published successfully'
|
||||
&& $job->body === 'Facebook Page (@InboxPlacement.io)'
|
||||
&& data_get($job->data, 'post_id') === $post->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('in-app failed notification falls back to the facebook page display name', function () {
|
||||
Event::fake();
|
||||
Queue::fake();
|
||||
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$post = Post::factory()->scheduled()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$publisher = Mockery::mock(FacebookPublisher::class);
|
||||
$publisher->shouldReceive('publish')->andThrow(new Exception('API error'));
|
||||
$this->app->instance(FacebookPublisher::class, $publisher);
|
||||
|
||||
(new PublishToSocialPlatform($postPlatform))->handle();
|
||||
|
||||
Queue::assertPushed(SendNotification::class, function (SendNotification $job) use ($post) {
|
||||
return $job->type === Type::PostFailed
|
||||
&& $job->title === 'Post failed to publish'
|
||||
&& $job->body === 'Failed on: Facebook Page (@InboxPlacement.io)'
|
||||
&& data_get($job->data, 'post_id') === $post->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('it retries with token refresh when token expires during publish', function () {
|
||||
Event::fake();
|
||||
|
||||
|
|
|
|||
29
tests/Unit/Mail/PostPublishFailedTest.php
Normal file
29
tests/Unit/Mail/PostPublishFailedTest.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Mail\PostPublishFailed;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\Workspace;
|
||||
|
||||
test('failed email falls back to the page display name when facebook has no username', function () {
|
||||
$workspace = Workspace::factory()->create(['name' => 'InboxPlacement.io']);
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$post = Post::factory()->failed()->create(['workspace_id' => $workspace->id]);
|
||||
PostPlatform::factory()->facebook()->failed()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
$mail = new PostPublishFailed($post);
|
||||
|
||||
$mail->assertSeeInHtml('Facebook Page (@InboxPlacement.io)');
|
||||
$mail->assertDontSeeInHtml('Facebook Page (@)');
|
||||
});
|
||||
71
tests/Unit/Mail/PostPublishedTest.php
Normal file
71
tests/Unit/Mail/PostPublishedTest.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Mail\PostPublished;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\Workspace;
|
||||
|
||||
test('published email falls back to the page display name when facebook has no username', function () {
|
||||
$workspace = Workspace::factory()->create(['name' => 'InboxPlacement.io']);
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$post = Post::factory()->published()->create(['workspace_id' => $workspace->id]);
|
||||
PostPlatform::factory()->facebook()->published()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
'platform_url' => 'https://www.facebook.com/permalink.php?story_fbid=pfbid0&id=61592851040951',
|
||||
]);
|
||||
|
||||
$mail = new PostPublished($post);
|
||||
|
||||
$mail->assertSeeInHtml('Facebook Page (@InboxPlacement.io)');
|
||||
$mail->assertDontSeeInHtml('Facebook Page (@)');
|
||||
$mail->assertSeeInHtml('https://www.facebook.com/permalink.php?story_fbid=pfbid0&id=61592851040951');
|
||||
});
|
||||
|
||||
test('published email uses the username when the display name is empty', function () {
|
||||
$workspace = Workspace::factory()->create(['name' => 'InboxPlacement.io']);
|
||||
$account = SocialAccount::factory()->bluesky()->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'username' => 'inboxplacementio.bsky.social',
|
||||
'display_name' => '',
|
||||
]);
|
||||
$post = Post::factory()->published()->create(['workspace_id' => $workspace->id]);
|
||||
PostPlatform::factory()->bluesky()->published()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
$mail = new PostPublished($post);
|
||||
|
||||
$mail->assertSeeInHtml('Bluesky (@inboxplacementio.bsky.social)');
|
||||
$mail->assertDontSeeInHtml('Bluesky (@)');
|
||||
});
|
||||
|
||||
test('published email omits empty parentheses when both identifiers are missing', function () {
|
||||
$workspace = Workspace::factory()->create(['name' => 'InboxPlacement.io']);
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => '',
|
||||
]);
|
||||
$post = Post::factory()->published()->create(['workspace_id' => $workspace->id]);
|
||||
PostPlatform::factory()->facebook()->published()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
$mail = new PostPublished($post);
|
||||
|
||||
$mail->assertSeeInHtml('Facebook Page');
|
||||
$mail->assertDontSeeInHtml('Facebook Page (@)');
|
||||
});
|
||||
|
|
@ -64,6 +64,84 @@
|
|||
expect($this->postPlatform->fresh()->display_name)->toBe('Snapshot Name');
|
||||
});
|
||||
|
||||
test('notificationLabel prefers username over display name', function () {
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => 'myfbpage',
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
expect($postPlatform->notificationLabel())->toBe('Facebook Page (@myfbpage)');
|
||||
});
|
||||
|
||||
test('notificationLabel falls back to display name when username is empty', function () {
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
expect($postPlatform->notificationLabel())->toBe('Facebook Page (@InboxPlacement.io)');
|
||||
});
|
||||
|
||||
test('notificationLabel uses username when display name is empty', function () {
|
||||
$account = SocialAccount::factory()->bluesky()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => 'inboxplacementio.bsky.social',
|
||||
'display_name' => '',
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->bluesky()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
expect($postPlatform->notificationLabel())->toBe('Bluesky (@inboxplacementio.bsky.social)');
|
||||
});
|
||||
|
||||
test('notificationLabel omits empty parentheses when both identifiers are missing', function () {
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => '',
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
]);
|
||||
|
||||
expect($postPlatform->notificationLabel())->toBe('Facebook Page');
|
||||
});
|
||||
|
||||
test('notificationLabel uses the snapshot when the account has been deleted', function () {
|
||||
$account = SocialAccount::factory()->facebook()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => null,
|
||||
'display_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
$postPlatform = PostPlatform::factory()->facebook()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $account->id,
|
||||
'platform' => $account->platform,
|
||||
'platform_name' => 'InboxPlacement.io',
|
||||
]);
|
||||
|
||||
$account->delete();
|
||||
|
||||
expect($postPlatform->fresh()->notificationLabel())->toBe('Facebook Page (@InboxPlacement.io)');
|
||||
});
|
||||
|
||||
test('markAsFailed clears stale platform_post_id and platform_url', function () {
|
||||
$this->postPlatform->update([
|
||||
'status' => Status::Published,
|
||||
|
|
|
|||
Loading…
Reference in a new issue