fix(channels): discord review round 5 — asset, channel fallback, media test

- 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.
This commit is contained in:
Paulo Castellano 2026-06-16 15:04:46 -03:00
parent c2dd4515b2
commit 5a54bf17fb
3 changed files with 47 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -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<DiscordChannel[]>(() => {
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<string | undefined>(() => {
if (props.meta?.channel_id) {
@ -202,7 +212,7 @@ const updateEmbed = (index: number, patch: Partial<EmbedDraft>) =>
<option value="">
{{ channelsLoading ? $t('posts.form.discord.loading_channels') : $t('posts.form.discord.select_channel') }}
</option>
<option v-for="channel in channels" :key="channel.id" :value="channel.id">#{{ channel.name }}</option>
<option v-for="channel in channelOptions" :key="channel.id" :value="channel.id">#{{ channel.name }}</option>
</select>
<InputError :message="channelError" />
</div>

View file

@ -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);