From d4db1eb2edd53abf7ef7395d3faffacc74481708 Mon Sep 17 00:00:00 2001
From: CJ_Clippy <cj@futureporn.net>
Date: Sun, 16 Mar 2025 13:54:34 -0800
Subject: [PATCH] improve fixtures

---
 .../lib/bright/oban_workers/process_posts.ex  |   4 +-
 apps/bright/lib/bright/platforms.ex           |   2 +
 .../lib/bright/platforms/platform_alias.ex    |   6 +-
 apps/bright/lib/bright/socials/x_post.ex      |  99 +++---
 .../oban_workers/process_posts_test.exs       |  56 ++--
 .../test/bright/socials/x_post_test.exs       | 300 ++++++++++--------
 .../support/fixtures/platforms_fixtures.ex    |  62 +++-
 .../test/support/fixtures/vtubers_fixtures.ex |  16 +-
 8 files changed, 319 insertions(+), 226 deletions(-)

diff --git a/apps/bright/lib/bright/oban_workers/process_posts.ex b/apps/bright/lib/bright/oban_workers/process_posts.ex
index 69f5ad8..9d1fa5d 100644
--- a/apps/bright/lib/bright/oban_workers/process_posts.ex
+++ b/apps/bright/lib/bright/oban_workers/process_posts.ex
@@ -42,8 +42,8 @@ defmodule Bright.ObanWorkers.ProcessPosts do
   end
 
   def process_post(post, known_platforms) do
-    with platforms <- XPost.get_platforms_mentioned(post, known_platforms),
-         true <- XPost.is_nsfw_live_announcement?(post, platforms, known_platforms),
+    with %{is_nsfw_live_announcement: is_live, platforms_mentioned: platforms} <-
+           XPost.parse(post, known_platforms),
          {:ok, _stream} <- create_stream(post, platforms) do
       :ok
     else
diff --git a/apps/bright/lib/bright/platforms.ex b/apps/bright/lib/bright/platforms.ex
index ed5295c..f16d9d0 100644
--- a/apps/bright/lib/bright/platforms.ex
+++ b/apps/bright/lib/bright/platforms.ex
@@ -7,6 +7,7 @@ defmodule Bright.Platforms do
   alias Bright.Repo
   alias Bright.Platforms.PlatformAlias
   alias Bright.Platforms.Platform
+  require Logger
 
   @doc """
   Returns the list of platforms.
@@ -134,6 +135,7 @@ defmodule Bright.Platforms do
   Do any of the A platforms match any of the B platforms?
   """
   def contains_platform?(a, b) do
+    Logger.debug("contains_platform? a=#{inspect(a)} b=#{inspect(b)}")
     Enum.any?(a, fn plat -> Enum.any?(b, &match_platform?(plat, &1)) end)
   end
 end
diff --git a/apps/bright/lib/bright/platforms/platform_alias.ex b/apps/bright/lib/bright/platforms/platform_alias.ex
index 886f4f1..dab0301 100644
--- a/apps/bright/lib/bright/platforms/platform_alias.ex
+++ b/apps/bright/lib/bright/platforms/platform_alias.ex
@@ -4,7 +4,7 @@ defmodule Bright.Platforms.PlatformAlias do
 
   schema "platform_aliases" do
     field :url, :string
-    field :platform_id, :id
+    belongs_to :platform, Bright.Platforms.Platform
 
     timestamps(type: :utc_datetime)
   end
@@ -12,7 +12,7 @@ defmodule Bright.Platforms.PlatformAlias do
   @doc false
   def changeset(platform_alias, attrs) do
     platform_alias
-    |> cast(attrs, [:url, :platform_id])
-    |> validate_required([:url, :platform_id])
+    |> cast(attrs, [:url])
+    |> validate_required([:url])
   end
 end
diff --git a/apps/bright/lib/bright/socials/x_post.ex b/apps/bright/lib/bright/socials/x_post.ex
index c0d469f..4c4401b 100644
--- a/apps/bright/lib/bright/socials/x_post.ex
+++ b/apps/bright/lib/bright/socials/x_post.ex
@@ -68,11 +68,22 @@ defmodule Bright.Socials.XPost do
   end
 
   def authored_by_vtuber?(x_post, vtuber) do
-    vtuber_path = URI.parse(vtuber.twitter).path
-    post_path = URI.parse(x_post.url).path
-    Logger.debug("vtuber_path=#{inspect(vtuber_path)} post_path=#{inspect(post_path)}")
+    Logger.debug("authored_by_vtuber? with x_post=#{inspect(x_post)} vtuber=#{inspect(vtuber)}")
 
-    String.starts_with?(post_path, vtuber_path)
+    vtuber_url = vtuber.twitter || ""
+    post_url = x_post.url || ""
+
+    case {URI.parse(vtuber_url), URI.parse(post_url)} do
+      {%URI{path: nil}, _} ->
+        raise "Invalid vtuber.twitter value: `#{inspect(vtuber.twitter)}`"
+
+      {_, %URI{path: nil}} ->
+        raise "Invalid x_post.url value: `#{inspect(x_post.url)}`"
+
+      {%URI{path: vtuber_path}, %URI{path: post_path}} ->
+        Logger.debug("vtuber_path=#{inspect(vtuber_path)} post_path=#{inspect(post_path)}")
+        String.starts_with?(String.downcase(post_path), String.downcase(vtuber_path))
+    end
   end
 
   def get_unprocessed_posts() do
@@ -87,15 +98,6 @@ defmodule Bright.Socials.XPost do
     uri.host || ""
   end
 
-  def includes_alias?(%XPost{raw: raw}, platform), do: includes_alias?(raw, platform)
-
-  def includes_alias?(raw, platform) do
-    case Map.get(platform, :platform_aliases, []) do
-      [] -> false
-      aliases -> Enum.any?(aliases, fn alias -> raw =~ extract_hostname(alias.url) end)
-    end
-  end
-
   @doc """
   Checks if the given raw text or XPost includes a reference to the specified platform.
 
@@ -109,27 +111,58 @@ defmodule Bright.Socials.XPost do
     - `true` if the platform's hostname or any of its aliases are found in the raw text.
     - `false` otherwise.
   """
+  def includes_platform?(
+        raw_text,
+        %Platform{url: url, platform_aliases: %Ecto.Association.NotLoaded{}} = platform
+      ) do
+    platform = Repo.preload(platform, :platform_aliases)
+    includes_platform?(raw_text, platform)
+  end
+
   def includes_platform?(%XPost{raw: raw}, platform) do
     includes_platform?(raw, platform)
   end
 
-  def includes_platform?(raw_text, %Platform{url: url} = platform)
-      when is_binary(raw_text) and is_binary(url) do
-    hostname_match = raw_text =~ extract_hostname(url)
-    alias_match = includes_alias?(raw_text, platform)
-    hostname_match || alias_match
+  def includes_platform?(raw_text, %Platform{url: url, platform_aliases: aliases}) do
+    host = URI.parse(url).host
+
+    Logger.debug(
+      "includes_platform? with raw_text=#{inspect(raw_text)} and aliases=#{inspect(aliases)}"
+    )
+
+    aliases =
+      aliases
+      # Assuming platform_aliases have an `alias` field.
+      |> Enum.map(& &1.alias)
+
+    values_to_match = [host | aliases]
+
+    Enum.any?(values_to_match, fn value ->
+      String.contains?(raw_text, value)
+    end)
   end
 
   def includes_platform?(_, _), do: false
 
-  def get_platforms_mentioned(%XPost{raw: raw}, [%Platform{} = platforms]) do
-    get_platforms_mentioned(raw, platforms)
-  end
-
   def get_platforms_mentioned(raw_text, platforms) do
     Enum.filter(platforms, &includes_platform?(raw_text, &1))
   end
 
+  def parse(%XPost{} = post) do
+    known_platforms = Platforms.list_platforms()
+    parse(post, known_platforms)
+  end
+
+  def parse(%XPost{vtuber: vtuber} = post, known_platforms) when is_list(known_platforms) do
+    is_nsfw_live_announcement = is_nsfw_live_announcement?(post, known_platforms)
+    platforms_mentioned = get_platforms_mentioned(post, known_platforms)
+
+    %{
+      is_nsfw_live_announcement: is_nsfw_live_announcement,
+      platforms_mentioned: platforms_mentioned
+    }
+  end
+
   @doc """
   Is the post a valid NSFW livestream announcement?
 
@@ -144,35 +177,27 @@ defmodule Bright.Socials.XPost do
   """
   def is_nsfw_live_announcement?(
         %XPost{vtuber: vtuber} = post,
-        mentioned_platforms,
         known_platforms
       ) do
+    mentioned_platforms = get_platforms_mentioned(post, known_platforms)
     Logger.debug("Checking if post is NSFW live announcement: #{inspect(post)}")
 
     nsfw_platforms = Enum.filter(known_platforms, & &1.nsfw)
     sfw_platforms = Enum.reject(known_platforms, & &1.nsfw)
 
     conditions = [
-      {:not_rt, XPost.authored_by_vtuber?(post, vtuber)},
-      {:not_vod, not String.contains?(String.downcase(post.raw), "vod")},
-      {:contains_nsfw_link, Platforms.contains_platform?(mentioned_platforms, nsfw_platforms)},
-      {:no_sfw_link, not Platforms.contains_platform?(mentioned_platforms, sfw_platforms)}
+      {:is_not_rt, XPost.authored_by_vtuber?(post, vtuber)},
+      {:is_not_vod, not String.contains?(String.downcase(post.raw), "vod")},
+      {:is_nsfw_platform, Platforms.contains_platform?(mentioned_platforms, nsfw_platforms)},
+      {:is_not_sfw_platform, not Platforms.contains_platform?(mentioned_platforms, sfw_platforms)}
     ]
 
     Enum.reduce_while(conditions, true, fn {label, condition}, _acc ->
       if condition do
+        Logger.debug(">>> āœ…āœ…āœ… NSFW announcement check PASSED at: #{label}")
         {:cont, true}
       else
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
-        Logger.debug(">>> NSFW announcement check failed at: #{label}")
+        Logger.debug(">>> šŸš«šŸš«šŸš« NSFW announcement check FAILED at: #{label}")
         {:halt, false}
       end
     end)
diff --git a/apps/bright/test/bright/oban_workers/process_posts_test.exs b/apps/bright/test/bright/oban_workers/process_posts_test.exs
index de6484b..a1ae486 100644
--- a/apps/bright/test/bright/oban_workers/process_posts_test.exs
+++ b/apps/bright/test/bright/oban_workers/process_posts_test.exs
@@ -13,7 +13,7 @@ defmodule Bright.ProcessPostsTest do
   alias Bright.Socials
 
   setup do
-    vtuber = Bright.VtubersFixtures.vtuber_fixture()
+    vtuber = Bright.VtubersFixtures.vtuber_fixture(%{twitter: "https://x.com/fakename"})
 
     Platforms.create_platform(%{
       name: "Fansly",
@@ -36,15 +36,23 @@ defmodule Bright.ProcessPostsTest do
       nsfw: true
     })
 
-    Platforms.create_platform(%{
-      name: "Chaturbate",
-      slug: "chaturbate",
-      url: "https://chaturbate.com",
-      nsfw: true
+    {:ok, chaturbate} =
+      Platforms.create_platform(%{
+        name: "Chaturbate",
+        slug: "chaturbate",
+        url: "https://chaturbate.com",
+        nsfw: true
+      })
+
+    Platforms.create_platform_alias(%{
+      name: "example.buzz",
+      url: "https://example.buzz",
+      platform_id: chaturbate.id
     })
 
     posts = [
       # these posts are valid nsfw livestream announcements
+      # this post is valid because it's a fansly invite
       Socials.create_x_post(%{
         raw: "I'm going live! fansly.com/fakename <3",
         url: "https://x.com/fakename/status/283498235",
@@ -52,6 +60,7 @@ defmodule Bright.ProcessPostsTest do
         processed_at: nil,
         vtuber_id: vtuber.id
       }),
+      # this post is valid because it's an onlyfans invite
       Socials.create_x_post(%{
         raw: "gm! tiem for sex breakfast https://onlyfans.com/fakename",
         url: "https://x.com/fakename/status/283498234",
@@ -59,6 +68,7 @@ defmodule Bright.ProcessPostsTest do
         processed_at: nil,
         vtuber_id: vtuber.id
       }),
+      # this post is valid because it's a chaturbate invite
       Socials.create_x_post(%{
         raw: "ero strim rn http://chaturbate.com/fakename",
         url: "https://x.com/fakename/status/283498232",
@@ -66,7 +76,16 @@ defmodule Bright.ProcessPostsTest do
         processed_at: nil,
         vtuber_id: vtuber.id
       }),
+      # this post is valid because it's a fansly alias invite
+      Socials.create_x_post(%{
+        raw: "Join NOW for some fun! https://example.buzz",
+        url: "https://x.com/fakename/status/394848232",
+        date: DateTime.utc_now(:second),
+        processed_at: nil,
+        vtuber_id: vtuber.id
+      }),
       # these posts are NOT valid livestream invitations
+      # this post is not valid because it's a twitch invite
       Socials.create_x_post(%{
         raw: "Let's play a game http://twitch.tv/fakename",
         url: "https://x.com/fakename/status/283498343",
@@ -74,6 +93,7 @@ defmodule Bright.ProcessPostsTest do
         processed_at: nil,
         vtuber_id: vtuber.id
       }),
+      # this post is not valid because it contains a sfw platform
       Socials.create_x_post(%{
         raw:
           "Be sure to follow me on my socials http://chaturbate.com/fakename http://twitch.tv/fakename http://onlyfans.com/fakename http://linktree.com/fakename",
@@ -90,38 +110,18 @@ defmodule Bright.ProcessPostsTest do
   describe "ProcessPosts" do
     import Bright.StreamsFixtures
 
-    @tag :integration
-    test "detects platforms based on known platform aliases" do
-      "@todo implement" |> flunk
-    end
-
     @tag :integration
     test "create a stream for each post containing an invite link" do
       {:ok, _} = perform_job(Bright.ObanWorkers.ProcessPosts, %{})
       streams = Repo.all(Stream)
 
-      # Assert there are exactly 3 streams
-      assert length(streams) == 3
+      assert length(streams) == 4
     end
 
     @tag :integration
     test "mark posts as processed" do
       {:ok, number_processed_posts} = perform_job(Bright.ObanWorkers.ProcessPosts, %{})
-      assert number_processed_posts === 3
+      assert number_processed_posts === 6
     end
   end
-
-  # @tag :integration
-  # test "torrent creation" do
-  #   stream = stream_fixture()
-  #   vod = vod_fixture(%{torrent: nil, stream_id: stream.id, s3_cdn_url: @test_video_url})
-
-  #   {:ok, %Torrent{} = torrent} =
-  #     perform_job(Bright.ObanWorkers.CreateTorrent, %{vod_id: vod.id})
-
-  #   assert is_number(torrent.id)
-  #   assert Regex.match?(~r/^magnet:/, torrent.magnet)
-  #   assert Regex.match?(~r/([A-F\d]+)\b/i, torrent.info_hash_v1)
-  #   assert Regex.match?(~r/([A-F\d]+)\b/i, torrent.info_hash_v2)
-  # end
 end
diff --git a/apps/bright/test/bright/socials/x_post_test.exs b/apps/bright/test/bright/socials/x_post_test.exs
index 5482c15..050f5ea 100644
--- a/apps/bright/test/bright/socials/x_post_test.exs
+++ b/apps/bright/test/bright/socials/x_post_test.exs
@@ -36,6 +36,28 @@ defmodule Bright.XPostTest do
 
       assert not XPost.authored_by_vtuber?(x_post, vtuberB)
     end
+
+    @tag :unit
+    test "raises an error when the x_post is lacking a url" do
+      vtuber = VtubersFixtures.projektmelody_fixture()
+      # Simulate a post without a URL
+      x_post = %{url: nil}
+
+      assert_raise RuntimeError, ~r/Invalid x_post.url value/, fn ->
+        XPost.authored_by_vtuber?(x_post, vtuber)
+      end
+    end
+
+    @tag :unit
+    test "raises an error when the vtuber is lacking a twitter url" do
+      # Simulate a vtuber without a Twitter URL
+      vtuber = VtubersFixtures.projektmelody_fixture(%{twitter: nil})
+      x_post = SocialsFixtures.x_post_fixture(%{vtuber_id: vtuber.id})
+
+      assert_raise RuntimeError, ~r/Invalid vtuber.twitter value/, fn ->
+        XPost.authored_by_vtuber?(x_post, vtuber)
+      end
+    end
   end
 
   describe "get_new_posts" do
@@ -92,22 +114,56 @@ defmodule Bright.XPostTest do
     end
   end
 
+  describe "parse/2" do
+    setup do
+      vtuber = VtubersFixtures.projektmelody_fixture()
+
+      post =
+        SocialsFixtures.x_post_fixture(%{
+          raw: "check me out LIVE at https://chaturbate.com/projektmelody",
+          vtuber_id: vtuber.id
+        })
+        |> Repo.preload(:vtuber)
+
+      known_platforms =
+        PlatformsFixtures.known_platforms_fixture()
+
+      {:ok, known_platforms: known_platforms, vtuber: vtuber, post: post}
+    end
+
+    @tag :unit
+    test "get is_nsfw_live_announcement", %{known_platforms: known_platforms, post: post} do
+      %{is_nsfw_live_announcement: is_live} = XPost.parse(post, known_platforms)
+      assert is_live
+    end
+
+    test "get platforms_mentioned", %{
+      known_platforms: known_platforms,
+      post: post
+    } do
+      %{platforms_mentioned: platforms_mentioned} = XPost.parse(post, known_platforms)
+      assert length(platforms_mentioned) > 3
+    end
+  end
+
   describe "includes_alias?/2" do
     setup do
-      ytmnd =
-        %Platform{
+      {:ok, ytmnd} =
+        Platforms.create_platform(%{
           name: "You're The Man Now Dog",
           slug: "ytmnd",
           url: "https://blueballfixed.ytmnd.com",
           nsfw: false
-        }
-        |> Repo.insert!()
+        })
 
-      %PlatformAlias{url: "https://shorturl.at/lZ3NM", platform_id: ytmnd.id}
-      |> Repo.insert!()
+      Platforms.create_platform_alias(%{url: "https://shorturl.at/lZ3NM", platform_id: ytmnd.id})
 
-      %Platform{name: "Twitch", slug: "twitch", url: "https://twitch.tv", nsfw: false}
-      |> Repo.insert!()
+      Platforms.create_platform(%{
+        name: "Twitch",
+        slug: "twitch",
+        url: "https://twitch.tv",
+        nsfw: false
+      })
 
       :ok
     end
@@ -186,49 +242,13 @@ defmodule Bright.XPostTest do
 
   describe "get_platforms_mentioned" do
     setup %{} do
-      assert {:ok, %Platform{}} =
-               Platforms.create_platform(%{
-                 name: "Chaturbate",
-                 slug: "chaturbate",
-                 url: "https://chaturbate.com"
-               })
+      known_platforms = PlatformsFixtures.known_platforms_fixture()
 
-      assert {:ok, %Platform{}} =
-               Platforms.create_platform(%{
-                 name: "OnlyFans",
-                 slug: "onlyfans",
-                 url: "https://onlyfans.com"
-               })
-
-      # seed the test db with some platforms
-      assert {:ok, %Platform{} = fanslyPlatform} =
-               Platforms.create_platform(%{
-                 name: "Fansly",
-                 slug: "fansly",
-                 url: "https://fansly.com"
-               })
-
-      assert {:ok, %Platform{}} =
-               Platforms.create_platform(%{
-                 name: "Twitch",
-                 slug: "twitch",
-                 url: "https://twitch.tv"
-               })
-
-      IO.puts("fanslyPlatform=#{inspect(fanslyPlatform)} id=#{fanslyPlatform.id}")
-
-      assert {:ok, %PlatformAlias{} = platAlias} =
-               Platforms.create_platform_alias(%{
-                 url: "https://melody.buzz",
-                 platform_id: fanslyPlatform.id
-               })
-
-      IO.puts("platAlias=#{inspect(platAlias)}")
-
-      :ok
+      {:ok, known_platforms: known_platforms}
     end
 
     @tag :unit
+    @tag :taco
     test "post with no links" do
       platforms = Platforms.list_platforms()
 
@@ -331,15 +351,14 @@ defmodule Bright.XPostTest do
     end
 
     @tag :unit
-    test "post with 3 platform invites 3" do
-      platforms = Platforms.list_platforms()
-
+    @tag :taco
+    test "post with 3 platform invites 3", %{known_platforms: known_platforms} do
       expected_platform_names = ["Fansly", "OnlyFans", "Chaturbate"]
 
       actual_platform_names =
         XPost.get_platforms_mentioned(
           XPostsFixtures.fixture_live_3() |> Map.get(:raw),
-          platforms
+          known_platforms
         )
         # Extract only names
         |> Enum.map(& &1.name)
@@ -348,8 +367,7 @@ defmodule Bright.XPostTest do
     end
 
     @tag :unit
-    test "post with a platform alias" do
-      known_platforms = Platforms.list_platforms()
+    test "post with a platform alias", %{known_platforms: known_platforms} do
       expected_platform_names = ["Fansly"]
 
       actual_platform_names =
@@ -363,109 +381,109 @@ defmodule Bright.XPostTest do
     end
   end
 
-  describe "is_nsfw_live_announcement?/3" do
-    setup do
-      vtuber = VtubersFixtures.projektmelody_fixture()
+  # describe "is_nsfw_live_announcement?/3" do
+  #   setup do
+  #     vtuber = VtubersFixtures.projektmelody_fixture()
 
-      {:ok, x_post} =
-        Socials.create_x_post(%{
-          raw: "I'm going live https://chaturbate.com/projektmelody",
-          url: "https://x.com/projektmelody/status/1234",
-          date: DateTime.utc_now(:second),
-          vtuber_id: vtuber.id
-        })
+  #     {:ok, x_post} =
+  #       Socials.create_x_post(%{
+  #         raw: "I'm going live https://chaturbate.com/projektmelody",
+  #         url: "https://x.com/projektmelody/status/1234",
+  #         date: DateTime.utc_now(:second),
+  #         vtuber_id: vtuber.id
+  #       })
 
-      x_post = Repo.preload(x_post, :vtuber)
-      known_platforms = PlatformsFixtures.known_platforms_fixture()
+  #     x_post = Repo.preload(x_post, :vtuber)
+  #     known_platforms = PlatformsFixtures.known_platforms_fixture()
 
-      mentioned_platforms = [
-        PlatformsFixtures.onlyfans_fixture(),
-        PlatformsFixtures.chaturbate_fixture(),
-        PlatformsFixtures.fansly_fixture()
-      ]
+  #     mentioned_platforms = [
+  #       PlatformsFixtures.onlyfans_fixture(),
+  #       PlatformsFixtures.chaturbate_fixture(),
+  #       PlatformsFixtures.fansly_fixture()
+  #     ]
 
-      {:ok,
-       vtuber: vtuber,
-       x_post: x_post,
-       known_platforms: known_platforms,
-       mentioned_platforms: mentioned_platforms}
-    end
+  #     {:ok,
+  #      vtuber: vtuber,
+  #      x_post: x_post,
+  #      known_platforms: known_platforms,
+  #      mentioned_platforms: mentioned_platforms}
+  #   end
 
-    @tag :integration
-    test "should return false when receiving a XPost linking to a SFW platform", %{
-      vtuber: vtuber,
-      x_post: x_post,
-      known_platforms: known_platforms
-    } do
-      mentioned_platforms = [
-        PlatformsFixtures.twitch_fixture(),
-        PlatformsFixtures.fansly_fixture(),
-        PlatformsFixtures.onlyfans_fixture(),
-        PlatformsFixtures.chaturbate_fixture()
-      ]
+  #   @tag :integration
+  #   test "should return false when receiving a XPost linking to a SFW platform", %{
+  #     vtuber: vtuber,
+  #     x_post: x_post,
+  #     known_platforms: known_platforms
+  #   } do
+  #     mentioned_platforms = [
+  #       PlatformsFixtures.twitch_fixture(),
+  #       PlatformsFixtures.fansly_fixture(),
+  #       PlatformsFixtures.onlyfans_fixture(),
+  #       PlatformsFixtures.chaturbate_fixture()
+  #     ]
 
-      assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
-    end
+  #     assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+  #   end
 
-    test "should return true when receiving an XPost with only Chaturbate mentioned", %{
-      vtuber: vtuber,
-      x_post: x_post,
-      known_platforms: known_platforms,
-      mentioned_platforms: mentioned_platforms
-    } do
-      mentioned_platforms = [
-        PlatformsFixtures.chaturbate_fixture()
-      ]
+  #   test "should return true when receiving an XPost with only Chaturbate mentioned", %{
+  #     vtuber: vtuber,
+  #     x_post: x_post,
+  #     known_platforms: known_platforms,
+  #     mentioned_platforms: mentioned_platforms
+  #   } do
+  #     mentioned_platforms = [
+  #       PlatformsFixtures.chaturbate_fixture()
+  #     ]
 
-      assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
-    end
+  #     assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+  #   end
 
-    test "should return true when receiving an XPost with only NSFW platforms mentioned", %{
-      vtuber: vtuber,
-      x_post: x_post,
-      known_platforms: known_platforms,
-      mentioned_platforms: mentioned_platforms
-    } do
-      assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
-    end
+  #   test "should return true when receiving an XPost with only NSFW platforms mentioned", %{
+  #     vtuber: vtuber,
+  #     x_post: x_post,
+  #     known_platforms: known_platforms,
+  #     mentioned_platforms: mentioned_platforms
+  #   } do
+  #     assert XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+  #   end
 
-    test "should return false when the XPost is a retweet", %{
-      known_platforms: known_platforms,
-      mentioned_platforms: mentioned_platforms
-    } do
-      vtuber = VtubersFixtures.el_xox_fixture()
+  #   test "should return false when the XPost is a retweet", %{
+  #     known_platforms: known_platforms,
+  #     mentioned_platforms: mentioned_platforms
+  #   } do
+  #     vtuber = VtubersFixtures.el_xox_fixture()
 
-      x_post = %XPost{
-        url: "https://x.com/mangel0399/status/1898602105851506907",
-        raw: "#34_XoX",
-        date: ~U[2025-03-09T05:09:51.000Z],
-        processed_at: nil,
-        vtuber_id: vtuber.id
-      }
+  #     x_post = %XPost{
+  #       url: "https://x.com/mangel0399/status/1898602105851506907",
+  #       raw: "#34_XoX",
+  #       date: ~U[2025-03-09T05:09:51.000Z],
+  #       processed_at: nil,
+  #       vtuber_id: vtuber.id
+  #     }
 
-      x_post = Repo.preload(x_post, :vtuber)
+  #     x_post = Repo.preload(x_post, :vtuber)
 
-      assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
-    end
+  #     assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+  #   end
 
-    test "should return false when receiving an XPost with `vod/i`", %{
-      known_platforms: known_platforms
-    } do
-      vtuber = VtubersFixtures.el_xox_fixture()
+  #   test "should return false when receiving an XPost with `vod/i`", %{
+  #     known_platforms: known_platforms
+  #   } do
+  #     vtuber = VtubersFixtures.el_xox_fixture()
 
-      x_post = %XPost{
-        raw:
-          "IRL JOI handcam stream! Listen to my instructions and stroke your cock for me until you cum šŸ†šŸ’¦\n\nThe rest of the VOD is available here for Tier 1 subscribers or for $10! šŸ’›\nā–¶ļø https://fansly.com/post/755934614",
-        url: "https://x.com/el_XoX34/status/1900275678152712493",
-        date: ~U[2025-03-09T05:09:51.000Z],
-        processed_at: nil,
-        vtuber_id: vtuber.id
-      }
+  #     x_post = %XPost{
+  #       raw:
+  #         "IRL JOI handcam stream! Listen to my instructions and stroke your cock for me until you cum šŸ†šŸ’¦\n\nThe rest of the VOD is available here for Tier 1 subscribers or for $10! šŸ’›\nā–¶ļø https://fansly.com/post/755934614",
+  #       url: "https://x.com/el_XoX34/status/1900275678152712493",
+  #       date: ~U[2025-03-09T05:09:51.000Z],
+  #       processed_at: nil,
+  #       vtuber_id: vtuber.id
+  #     }
 
-      x_post = Repo.preload(x_post, :vtuber)
+  #     x_post = Repo.preload(x_post, :vtuber)
 
-      mentioned_platforms = [PlatformsFixtures.fansly_fixture()]
-      assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
-    end
-  end
+  #     mentioned_platforms = [PlatformsFixtures.fansly_fixture()]
+  #     assert not XPost.is_nsfw_live_announcement?(x_post, mentioned_platforms, known_platforms)
+  #   end
+  # end
 end
diff --git a/apps/bright/test/support/fixtures/platforms_fixtures.ex b/apps/bright/test/support/fixtures/platforms_fixtures.ex
index 9a1d8ab..a841153 100644
--- a/apps/bright/test/support/fixtures/platforms_fixtures.ex
+++ b/apps/bright/test/support/fixtures/platforms_fixtures.ex
@@ -5,6 +5,7 @@ defmodule Bright.PlatformsFixtures do
   """
 
   alias Bright.Platforms.Platform
+  alias Bright.Platforms.PlatformAlias
 
   @doc """
   Generate a platform.
@@ -36,34 +37,79 @@ defmodule Bright.PlatformsFixtures do
   end
 
   def twitch_fixture() do
-    %Platform{name: "Twitch", slug: "twitch", url: "https://twitch.tv", nsfw: false}
+    {:ok, platform} =
+      %{name: "Twitch", slug: "twitch", url: "https://twitch.tv", nsfw: false}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def fansly_fixture() do
-    %Platform{name: "Fansly", slug: "fansly", url: "https://fansly.com", nsfw: true}
+    {:ok, platform} =
+      %{
+        name: "Fansly",
+        slug: "fansly",
+        url: "https://fansly.com",
+        nsfw: true
+      }
+      |> Bright.Platforms.create_platform()
+
+    {:ok, alias} =
+      %{
+        url: "https://melody.buzz",
+        platform_id: platform.id
+      }
+      |> Bright.Platforms.create_platform_alias()
+
+    # Repo.preload(platform, :platform_aliases)
+    platform
   end
 
   def chaturbate_fixture() do
-    %Platform{name: "Chaturbate", slug: "chaturbate", url: "https://chaturbate.com", nsfw: true}
+    {:ok, platform} =
+      %{name: "Chaturbate", slug: "chaturbate", url: "https://chaturbate.com", nsfw: true}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def onlyfans_fixture() do
-    %Platform{name: "OnlyFans", slug: "onlyfans", url: "https://onlyfans.com", nsfw: true}
+    {:ok, platform} =
+      %{name: "OnlyFans", slug: "onlyfans", url: "https://onlyfans.com", nsfw: true}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def linktree_fixture() do
-    %Platform{name: "Linktree", slug: "linktree", url: "https://linktr.ee", nsfw: false}
+    {:ok, platform} =
+      %{name: "Linktree", slug: "linktree", url: "https://linktr.ee", nsfw: false}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def discord_fixture() do
-    %Platform{name: "Discord", slug: "discord", url: "https://discord.com", nsfw: false}
+    {:ok, platform} =
+      %{name: "Discord", slug: "discord", url: "https://discord.com", nsfw: false}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def carrd_fixture() do
-    %Platform{name: "Carrd", slug: "carrd", url: "https://carrd.co", nsfw: false}
+    {:ok, platform} =
+      %{name: "Carrd", slug: "carrd", url: "https://carrd.co", nsfw: false}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 
   def throne_fixture() do
-    %Platform{name: "Throne", slug: "throne", url: "https://throne.com", nsfw: false}
+    {:ok, platform} =
+      %{name: "Throne", slug: "throne", url: "https://throne.com", nsfw: false}
+      |> Bright.Platforms.create_platform()
+
+    platform
   end
 end
diff --git a/apps/bright/test/support/fixtures/vtubers_fixtures.ex b/apps/bright/test/support/fixtures/vtubers_fixtures.ex
index 40ffdf5..2151196 100644
--- a/apps/bright/test/support/fixtures/vtubers_fixtures.ex
+++ b/apps/bright/test/support/fixtures/vtubers_fixtures.ex
@@ -43,29 +43,31 @@ defmodule Bright.VtubersFixtures do
     vtuber
   end
 
-  def el_xox_fixture() do
+  def el_xox_fixture(attrs \\ %{}) do
     {:ok, vtuber} =
-      %{
+      attrs
+      |> Enum.into(%{
         display_name: "el_XoX",
         slug: "el_xox",
         twitter: "https://x.com/el_XoX34",
         theme_color: "#c061cb",
         image: "https://futureporn-b2.b-cdn.net/el_xox.jpg"
-      }
+      })
       |> Bright.Vtubers.create_vtuber()
 
     vtuber
   end
 
-  def projektmelody_fixture() do
+  def projektmelody_fixture(attrs \\ %{}) do
     {:ok, vtuber} =
-      %{
+      attrs
+      |> Enum.into(%{
         display_name: "ProjektMelody",
         slug: "projektmelody",
-        twitter: "https://x.com/projektmelody",
+        twitter: "https://x.com/ProjektMelody",
         theme_color: "#c061cb",
         image: "https://futureporn-b2.b-cdn.net/projekt-melody.jpg"
-      }
+      })
       |> Bright.Vtubers.create_vtuber()
 
     vtuber