fix: LinkedIn company page post URL missing the post ID (#272)
* fix: LinkedIn company page post URL missing the post ID
LinkedInPagePublisher::postUrl() built a company/{username}/posts/
URL but never interpolated $postId into it, so the "view on LinkedIn"
link for company page posts always ended at the trailing slash.
Per LinkedIn's Posts API docs, the correct public URL for any
published post (member or organization) is always
feed/update/{postId} — so the override is removed and the class now
inherits the correct base implementation.
Closes #271
* address code review: backfill historical URLs, fix stale docblock, close test gaps
- Add social:backfill-linkedin-page-post-urls to recompute platform_url
for LinkedIn Page posts published before the fix, whose broken
company/{username}/posts/ URL was already persisted and never
recomputed. Supports --dry-run.
- Fix AbstractLinkedInPublisher::postUrl() docblock, which still
claimed company pages override it after the override was removed.
- LinkedInPagePublisherTest: assert result['url'] in the image and
carousel publish tests (previously only asserted result['id']);
add a null-postId case; collapse the two username-branch tests
(now dead code) into one dataset-driven test plus a dedicated
null-postId test.
This commit is contained in:
parent
eb52b6b699
commit
29e90c52af
5 changed files with 176 additions and 30 deletions
49
app/Console/Commands/BackfillLinkedInPagePostUrls.php
Normal file
49
app/Console/Commands/BackfillLinkedInPagePostUrls.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\PostPlatform\Status;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Models\PostPlatform;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* One-time repair for LinkedIn Page posts published before the fix for
|
||||
* https://github.com/trypostit/trypost/issues/271 — LinkedInPagePublisher::postUrl()
|
||||
* built "company/{username}/posts/" without appending the post ID, so those rows
|
||||
* had that broken, unusable URL persisted to platform_url.
|
||||
*/
|
||||
class BackfillLinkedInPagePostUrls extends Command
|
||||
{
|
||||
protected $signature = 'social:backfill-linkedin-page-post-urls {--dry-run : Preview affected rows without writing changes}';
|
||||
|
||||
protected $description = 'Recompute platform_url for LinkedIn Page posts published with the pre-fix company/{username}/posts/ URL (issue #271)';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$count = 0;
|
||||
|
||||
PostPlatform::query()
|
||||
->where('platform', Platform::LinkedInPage)
|
||||
->where('status', Status::Published)
|
||||
->whereNotNull('platform_post_id')
|
||||
->where('platform_url', 'like', 'https://www.linkedin.com/company/%/posts/')
|
||||
->each(function (PostPlatform $postPlatform) use ($dryRun, &$count) {
|
||||
$correctedUrl = "https://www.linkedin.com/feed/update/{$postPlatform->platform_post_id}";
|
||||
|
||||
$this->line("{$postPlatform->id}: {$postPlatform->platform_url} -> {$correctedUrl}");
|
||||
|
||||
if (! $dryRun) {
|
||||
$postPlatform->update(['platform_url' => $correctedUrl]);
|
||||
}
|
||||
|
||||
$count++;
|
||||
});
|
||||
|
||||
$verb = $dryRun ? 'Would fix' : 'Fixed';
|
||||
$this->info("{$verb} {$count} LinkedIn Page post URL(s).");
|
||||
}
|
||||
}
|
||||
|
|
@ -47,8 +47,10 @@ abstract protected function platform(): Platform;
|
|||
abstract protected function authorUrn(): string;
|
||||
|
||||
/**
|
||||
* Public URL of the created post. Defaults to the member feed update;
|
||||
* company pages override it.
|
||||
* Public URL of the created post. Per LinkedIn's Posts API docs, the
|
||||
* feed/update permalink applies to any published post regardless of
|
||||
* whether the author is a member or an organization, so subclasses
|
||||
* (member vs. company page) share this implementation.
|
||||
*/
|
||||
protected function postUrl(?string $postId): ?string
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,15 +31,4 @@ protected function authorUrn(): string
|
|||
|
||||
return "urn:li:organization:{$organizationId}";
|
||||
}
|
||||
|
||||
protected function postUrl(?string $postId): ?string
|
||||
{
|
||||
if (! $postId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->account->username
|
||||
? "https://www.linkedin.com/company/{$this->account->username}/posts/"
|
||||
: "https://www.linkedin.com/feed/update/{$postId}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
tests/Feature/Commands/BackfillLinkedInPagePostUrlsTest.php
Normal file
103
tests/Feature/Commands/BackfillLinkedInPagePostUrlsTest.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\PostPlatform\Status;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
$this->workspace = Workspace::factory()->create(['user_id' => $this->user->id]);
|
||||
$this->socialAccount = SocialAccount::factory()->linkedinPage()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'username' => 'testcompany',
|
||||
]);
|
||||
$this->post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('it corrects broken linkedin page post urls', function () {
|
||||
$broken = PostPlatform::factory()->published()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
'platform' => Platform::LinkedInPage,
|
||||
'platform_post_id' => 'urn:li:share:1234567890',
|
||||
'platform_url' => 'https://www.linkedin.com/company/testcompany/posts/',
|
||||
]);
|
||||
|
||||
$this->artisan('social:backfill-linkedin-page-post-urls')->assertSuccessful();
|
||||
|
||||
$broken->refresh();
|
||||
expect($broken->platform_url)->toBe('https://www.linkedin.com/feed/update/urn:li:share:1234567890');
|
||||
});
|
||||
|
||||
test('it leaves already-correct linkedin page urls untouched', function () {
|
||||
$correct = PostPlatform::factory()->published()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
'platform' => Platform::LinkedInPage,
|
||||
'platform_post_id' => 'urn:li:share:1234567890',
|
||||
'platform_url' => 'https://www.linkedin.com/feed/update/urn:li:share:1234567890',
|
||||
]);
|
||||
|
||||
$this->artisan('social:backfill-linkedin-page-post-urls')->assertSuccessful();
|
||||
|
||||
$correct->refresh();
|
||||
expect($correct->platform_url)->toBe('https://www.linkedin.com/feed/update/urn:li:share:1234567890');
|
||||
});
|
||||
|
||||
test('it does not touch other platforms with a similarly shaped url', function () {
|
||||
$other = PostPlatform::factory()->published()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => SocialAccount::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'platform' => Platform::LinkedIn,
|
||||
]),
|
||||
'platform' => Platform::LinkedIn,
|
||||
'platform_post_id' => 'urn:li:share:9999999999',
|
||||
'platform_url' => 'https://www.linkedin.com/company/testcompany/posts/',
|
||||
]);
|
||||
|
||||
$this->artisan('social:backfill-linkedin-page-post-urls')->assertSuccessful();
|
||||
|
||||
$other->refresh();
|
||||
expect($other->platform_url)->toBe('https://www.linkedin.com/company/testcompany/posts/');
|
||||
});
|
||||
|
||||
test('dry run previews without writing changes', function () {
|
||||
$broken = PostPlatform::factory()->published()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
'platform' => Platform::LinkedInPage,
|
||||
'platform_post_id' => 'urn:li:share:1234567890',
|
||||
'platform_url' => 'https://www.linkedin.com/company/testcompany/posts/',
|
||||
]);
|
||||
|
||||
$this->artisan('social:backfill-linkedin-page-post-urls', ['--dry-run' => true])->assertSuccessful();
|
||||
|
||||
$broken->refresh();
|
||||
expect($broken->platform_url)->toBe('https://www.linkedin.com/company/testcompany/posts/');
|
||||
});
|
||||
|
||||
test('it ignores linkedin page rows that are not published', function () {
|
||||
$failed = PostPlatform::factory()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
'platform' => Platform::LinkedInPage,
|
||||
'status' => Status::Failed,
|
||||
'platform_post_id' => 'urn:li:share:1234567890',
|
||||
'platform_url' => 'https://www.linkedin.com/company/testcompany/posts/',
|
||||
]);
|
||||
|
||||
$this->artisan('social:backfill-linkedin-page-post-urls')->assertSuccessful();
|
||||
|
||||
$failed->refresh();
|
||||
expect($failed->platform_url)->toBe('https://www.linkedin.com/company/testcompany/posts/');
|
||||
});
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
expect($result)->toHaveKey('id');
|
||||
expect($result)->toHaveKey('url');
|
||||
expect($result['id'])->toBe('urn:li:share:1234567890');
|
||||
expect($result['url'])->toContain('linkedin.com/company/testcompany/posts/');
|
||||
expect($result['url'])->toBe('https://www.linkedin.com/feed/update/urn:li:share:1234567890');
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/rest/posts')
|
||||
|
|
@ -203,20 +203,8 @@
|
|||
});
|
||||
});
|
||||
|
||||
test('linkedin page publisher builds correct company url when username present', function () {
|
||||
Http::fake([
|
||||
config('trypost.platforms.linkedin-page.api').'/rest/posts' => Http::response(null, 201, [
|
||||
'x-restli-id' => 'urn:li:share:1234567890',
|
||||
]),
|
||||
]);
|
||||
|
||||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['url'])->toContain('linkedin.com/company/testcompany/posts/');
|
||||
});
|
||||
|
||||
test('linkedin page publisher builds feed url when username missing', function () {
|
||||
$this->socialAccount->update(['username' => null]);
|
||||
test('linkedin page publisher builds feed url from the post id regardless of username', function (?string $username) {
|
||||
$this->socialAccount->update(['username' => $username]);
|
||||
|
||||
Http::fake([
|
||||
config('trypost.platforms.linkedin-page.api').'/rest/posts' => Http::response(null, 201, [
|
||||
|
|
@ -226,7 +214,20 @@
|
|||
|
||||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['url'])->toContain('linkedin.com/feed/update/urn:li:share:1234567890');
|
||||
expect($result['url'])->toBe('https://www.linkedin.com/feed/update/urn:li:share:1234567890');
|
||||
})->with([
|
||||
'username present' => ['testcompany'],
|
||||
'username missing' => [null],
|
||||
]);
|
||||
|
||||
test('linkedin page publisher returns a null url when the response has no post id', function () {
|
||||
Http::fake([
|
||||
config('trypost.platforms.linkedin-page.api').'/rest/posts' => Http::response(null, 201),
|
||||
]);
|
||||
|
||||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['url'])->toBeNull();
|
||||
});
|
||||
|
||||
test('linkedin page publisher can publish post with image using organization urn', function () {
|
||||
|
|
@ -271,6 +272,7 @@
|
|||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['id'])->toBe('urn:li:share:9999999999');
|
||||
expect($result['url'])->toBe('https://www.linkedin.com/feed/update/urn:li:share:9999999999');
|
||||
|
||||
Http::assertSent(fn ($request) => str_contains($request->url(), '/rest/images'));
|
||||
|
||||
|
|
@ -319,6 +321,7 @@
|
|||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['id'])->toBe('urn:li:share:orgcarousel');
|
||||
expect($result['url'])->toBe('https://www.linkedin.com/feed/update/urn:li:share:orgcarousel');
|
||||
|
||||
// Org carousel must author as the organization and send image URNs under `id` (not `media`).
|
||||
Http::assertSent(function ($request) {
|
||||
|
|
@ -385,7 +388,7 @@
|
|||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
||||
expect($result['id'])->toBe('urn:li:share:orgdoc999');
|
||||
expect($result['url'])->toContain('linkedin.com/company/testcompany/posts/');
|
||||
expect($result['url'])->toBe('https://www.linkedin.com/feed/update/urn:li:share:orgdoc999');
|
||||
|
||||
Http::assertSent(fn ($request) => str_contains($request->url(), '/rest/documents') && str_contains($request->url(), 'initializeUpload'));
|
||||
Http::assertSent(function ($request) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue