From 5a54bf17fb1b953d4335cf1589fe20258296f3cb Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Tue, 16 Jun 2026 15:04:46 -0300 Subject: [PATCH] =?UTF-8?q?fix(channels):=20discord=20review=20round=205?= =?UTF-8?q?=20=E2=80=94=20asset,=20channel=20fallback,=20media=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add a placeholder discord.png so the brand mark isn't a broken image (operators can swap in the official logo). - Keep a saved Discord channel selectable before the live channel list loads (or if the lookup is unavailable), so editing a post never loses the channel. - Cover the media multipart upload path (payload_json + files[N]) with a test. --- public/images/accounts/discord.png | Bin 0 -> 2028 bytes .../posts/editor/DiscordSettings.vue | 12 +++++- .../Services/Social/DiscordPublisherTest.php | 36 ++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 public/images/accounts/discord.png diff --git a/public/images/accounts/discord.png b/public/images/accounts/discord.png new file mode 100644 index 0000000000000000000000000000000000000000..92b7bde1d7323cdf7cfca58287ef677030a09489 GIT binary patch literal 2028 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 KMNUMnLSTaT2ATE% literal 0 HcmV?d00001 diff --git a/resources/js/components/posts/editor/DiscordSettings.vue b/resources/js/components/posts/editor/DiscordSettings.vue index 45141a0d..31a8c7e3 100644 --- a/resources/js/components/posts/editor/DiscordSettings.vue +++ b/resources/js/components/posts/editor/DiscordSettings.vue @@ -87,6 +87,16 @@ const channelId = computed({ set: (value: string) => updateMeta({ channel_id: value || null }), }); +// Keep a saved channel selectable even before the live list loads (or if the +// lookup is unavailable), so editing a post never visually "loses" its channel. +const channelOptions = computed(() => { + if (channelId.value && !channels.value.some((channel) => channel.id === channelId.value)) { + return [{ id: channelId.value, name: channelId.value }, ...channels.value]; + } + + return channels.value; +}); + const errors = usePageErrors(); const channelError = computed(() => { if (props.meta?.channel_id) { @@ -202,7 +212,7 @@ const updateEmbed = (index: number, patch: Partial) => - + diff --git a/tests/Feature/Services/Social/DiscordPublisherTest.php b/tests/Feature/Services/Social/DiscordPublisherTest.php index 34b77609..32bf2845 100644 --- a/tests/Feature/Services/Social/DiscordPublisherTest.php +++ b/tests/Feature/Services/Social/DiscordPublisherTest.php @@ -11,6 +11,7 @@ use App\Models\SocialAccount; use App\Models\User; use App\Models\Workspace; +use App\Services\Media\MediaOptimizer; use App\Services\Social\Discord\DiscordPublisher; use Illuminate\Support\Facades\Http; @@ -120,6 +121,41 @@ function fakeDiscord(array $messageResponse = ['id' => '777'], int $status = 200 }); }); +test('uploads media as a multipart attachment', function () { + $this->post->update([ + 'media' => [[ + 'id' => 'm1', + 'path' => 'media/2026-01/pic.jpg', + 'url' => 'https://example.com/media/2026-01/pic.jpg', + 'mime_type' => 'image/jpeg', + 'original_filename' => 'pic.jpg', + ]], + ]); + + $this->mock(MediaOptimizer::class) + ->shouldReceive('optimizeImage') + ->andReturnUsing(fn () => tap(tempnam(sys_get_temp_dir(), 'discord_test_'), fn ($f) => file_put_contents($f, str_repeat('x', 1024)))); + + Http::fake([ + config('trypost.platforms.discord.api').'/guilds/*/channels' => Http::response([['id' => '444555666', 'name' => 'general', 'type' => 0]], 200), + 'example.com/*' => Http::response(str_repeat('x', 1024), 200), + config('trypost.platforms.discord.api').'/channels/*/messages' => Http::response(['id' => '901'], 200), + ]); + + $result = $this->publisher->publish(($this->makePostPlatform)()); + + expect($result['id'])->toBe('901'); + + Http::assertSent(function ($request) { + if (! str_contains($request->url(), '/messages')) { + return false; + } + $names = collect($request->data())->pluck('name'); + + return $names->contains('payload_json') && $names->contains('files[0]'); + }); +}); + test('throws when no channel is selected', function () { expect(fn () => $this->publisher->publish(($this->makePostPlatform)(meta: []))) ->toThrow(DiscordPublishException::class);