From 2503ef6be194cf47ec53837c42b9a0e9a2d83923 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 10 Mar 2026 13:15:09 +0800 Subject: [PATCH 1/3] fix: handling of empty discord client event messages --- .../discord_client.ex | 9 +- .../discord_client_test.exs | 106 ++++++++++++++++++ test/test_helper.exs | 1 + 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 test/logflare/sources/source/webhook_notification_server/discord_client_test.exs diff --git a/lib/logflare/sources/source/webhook_notification_server/discord_client.ex b/lib/logflare/sources/source/webhook_notification_server/discord_client.ex index 5f17e009a0..4ea7758263 100644 --- a/lib/logflare/sources/source/webhook_notification_server/discord_client.ex +++ b/lib/logflare/sources/source/webhook_notification_server/discord_client.ex @@ -96,7 +96,14 @@ defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClient do defp discord_event_message(x) do timestamp = DateTime.from_unix!(x.body["timestamp"], :microsecond) |> DateTime.to_string() - {message, _} = String.split_at(x.body["event_message"], 1018) + + event_message = + case x.body["event_message"] do + msg when is_binary(msg) and msg != "" -> msg + _ -> Jason.encode!(x.body, pretty: true) + end + + {message, _} = String.split_at(event_message, 1018) %{name: timestamp, value: "```#{message}```"} end diff --git a/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs b/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs new file mode 100644 index 0000000000..f08ebc933c --- /dev/null +++ b/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs @@ -0,0 +1,106 @@ +defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClientTest do + use Logflare.DataCase, async: false + + import Mimic + + alias Logflare.Sources.Source.WebhookNotificationServer.DiscordClient + + setup :set_mimic_global + + setup do + insert(:plan) + user = insert(:user) + + source = + insert(:source, + user: user, + webhook_notification_url: "https://discord.com/api/webhooks/test" + ) + + [source: source] + end + + defp stub_tesla_ok(test_pid, ref) do + stub(Tesla, :post, fn _client, _url, payload -> + send(test_pid, {ref, payload}) + {:ok, %Tesla.Env{status: 200}} + end) + end + + defp make_log_event(source, body_overrides) do + timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) + + body = + Map.merge( + %{"timestamp" => timestamp_us, "event_message" => "default message"}, + body_overrides + ) + + %Logflare.LogEvent{body: body, source_id: source.id} + end + + describe "post/4" do + test "json stringifies body when event_message is nil", %{source: source} do + ref = make_ref() + stub_tesla_ok(self(), ref) + + timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) + body = %{"timestamp" => timestamp_us, "event_message" => nil, "foo" => "bar"} + event = %Logflare.LogEvent{body: body, source_id: source.id} + client = DiscordClient.new() + + assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) + assert_receive {^ref, payload}, 1000 + + [field] = payload.embeds |> hd() |> Map.get(:fields) + assert field.value =~ ~s("foo") + assert field.value =~ ~s("bar") + end + + test "json stringifies body when event_message is absent", %{source: source} do + ref = make_ref() + stub_tesla_ok(self(), ref) + + timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) + body = %{"timestamp" => timestamp_us, "count" => 42} + event = %Logflare.LogEvent{body: body, source_id: source.id} + client = DiscordClient.new() + + assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) + assert_receive {^ref, payload}, 1000 + + [field] = payload.embeds |> hd() |> Map.get(:fields) + assert field.value =~ ~s("count") + assert field.value =~ "42" + end + + test "includes event_message when present", %{source: source} do + ref = make_ref() + stub_tesla_ok(self(), ref) + + event = make_log_event(source, %{"event_message" => "hello world"}) + client = DiscordClient.new() + + assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) + assert_receive {^ref, payload}, 1000 + + [field] = payload.embeds |> hd() |> Map.get(:fields) + assert field.value == "```hello world```" + end + + test "truncates long event_message to 1018 chars", %{source: source} do + ref = make_ref() + stub_tesla_ok(self(), ref) + + long_message = String.duplicate("x", 2000) + event = make_log_event(source, %{"event_message" => long_message}) + client = DiscordClient.new() + + assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) + assert_receive {^ref, %{embeds: [%{fields: fields} | _]}}, 1000 + + # value is "``````" so message portion is value minus 6 backtick chars + assert String.length(fields |> hd() |> Map.get(:value)) < 1030 + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 3f43edc962..7a2bbb2b9d 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -25,6 +25,7 @@ Mimic.copy(Stripe.Invoice) Mimic.copy(Stripe.PaymentMethod) Mimic.copy(Stripe.Subscription) Mimic.copy(Stripe.SubscriptionItem.Usage) +Mimic.copy(Tesla) Mimic.copy(Tesla.Adapter.Finch) Mimic.copy(Logflare.Admin) From 87c2ed517ac64b6e58617ab01e716f3996e68a78 Mon Sep 17 00:00:00 2001 From: Ziinc Date: Thu, 12 Mar 2026 19:24:18 +0800 Subject: [PATCH 2/3] chore: condense tests --- .../discord_client_test.exs | 92 +++++++------------ 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs b/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs index f08ebc933c..b083d6c5e1 100644 --- a/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs +++ b/test/logflare/sources/source/webhook_notification_server/discord_client_test.exs @@ -20,65 +20,39 @@ defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClientTest do [source: source] end - defp stub_tesla_ok(test_pid, ref) do - stub(Tesla, :post, fn _client, _url, payload -> - send(test_pid, {ref, payload}) - {:ok, %Tesla.Env{status: 200}} - end) - end - - defp make_log_event(source, body_overrides) do - timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) - - body = - Map.merge( - %{"timestamp" => timestamp_us, "event_message" => "default message"}, - body_overrides - ) - - %Logflare.LogEvent{body: body, source_id: source.id} - end - describe "post/4" do - test "json stringifies body when event_message is nil", %{source: source} do - ref = make_ref() - stub_tesla_ok(self(), ref) - - timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) - body = %{"timestamp" => timestamp_us, "event_message" => nil, "foo" => "bar"} - event = %Logflare.LogEvent{body: body, source_id: source.id} - client = DiscordClient.new() - - assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) - assert_receive {^ref, payload}, 1000 - - [field] = payload.embeds |> hd() |> Map.get(:fields) - assert field.value =~ ~s("foo") - assert field.value =~ ~s("bar") - end - - test "json stringifies body when event_message is absent", %{source: source} do - ref = make_ref() - stub_tesla_ok(self(), ref) - - timestamp_us = DateTime.utc_now() |> DateTime.to_unix(:microsecond) - body = %{"timestamp" => timestamp_us, "count" => 42} - event = %Logflare.LogEvent{body: body, source_id: source.id} - client = DiscordClient.new() - - assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) - assert_receive {^ref, payload}, 1000 - - [field] = payload.embeds |> hd() |> Map.get(:fields) - assert field.value =~ ~s("count") - assert field.value =~ "42" + test "json stringifies body when event_message is nil or absent", %{source: source} do + for body <- [ + %{"timestamp" => 1_000_000, "event_message" => nil, "foo" => "bar"}, + %{"timestamp" => 1_000_000, "count" => 42} + ] do + ref = make_ref() + + stub(Tesla, :post, fn _client, _url, payload -> + send(self(), {ref, payload}) + {:ok, %Tesla.Env{status: 200}} + end) + + event = %Logflare.LogEvent{body: body, source_id: source.id} + client = DiscordClient.new() + + assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) + assert_receive {^ref, payload}, 1000 + + [field] = payload.embeds |> hd() |> Map.get(:fields) + assert field.value =~ ~s("timestamp") + end end test "includes event_message when present", %{source: source} do ref = make_ref() - stub_tesla_ok(self(), ref) - event = make_log_event(source, %{"event_message" => "hello world"}) + stub(Tesla, :post, fn _client, _url, payload -> + send(self(), {ref, payload}) + {:ok, %Tesla.Env{status: 200}} + end) + + event = build(:log_event, source: source, event_message: "hello world") client = DiscordClient.new() assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) @@ -88,18 +62,20 @@ defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClientTest do assert field.value == "```hello world```" end - test "truncates long event_message to 1018 chars", %{source: source} do + test "truncates long event_message to under 1030 chars", %{source: source} do ref = make_ref() - stub_tesla_ok(self(), ref) - long_message = String.duplicate("x", 2000) - event = make_log_event(source, %{"event_message" => long_message}) + stub(Tesla, :post, fn _client, _url, payload -> + send(self(), {ref, payload}) + {:ok, %Tesla.Env{status: 200}} + end) + + event = build(:log_event, source: source, event_message: String.duplicate("x", 2000)) client = DiscordClient.new() assert {:ok, _} = DiscordClient.post(client, source, 1, [event]) assert_receive {^ref, %{embeds: [%{fields: fields} | _]}}, 1000 - # value is "``````" so message portion is value minus 6 backtick chars assert String.length(fields |> hd() |> Map.get(:value)) < 1030 end end From 6c271dae7124303da5557a2a7d39b967c46cb3f8 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 10 Apr 2026 20:37:56 +0800 Subject: [PATCH 3/3] chore: PR comments --- .../source/webhook_notification_server/discord_client.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/logflare/sources/source/webhook_notification_server/discord_client.ex b/lib/logflare/sources/source/webhook_notification_server/discord_client.ex index 4ea7758263..8029e12c34 100644 --- a/lib/logflare/sources/source/webhook_notification_server/discord_client.ex +++ b/lib/logflare/sources/source/webhook_notification_server/discord_client.ex @@ -2,6 +2,8 @@ defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClient do @moduledoc false require Logger + import Logflare.Utils.Guards + alias LogflareWeb.Router.Helpers, as: Routes alias LogflareWeb.Endpoint @@ -99,7 +101,7 @@ defmodule Logflare.Sources.Source.WebhookNotificationServer.DiscordClient do event_message = case x.body["event_message"] do - msg when is_binary(msg) and msg != "" -> msg + msg when is_non_empty_binary(msg) -> msg _ -> Jason.encode!(x.body, pretty: true) end