From 2f88e64b1a65f31bdf79f263fbd71543e3d8fcc3 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 22 Jan 2026 12:44:45 -0500 Subject: [PATCH] fix: strip www. prefix in normalizeProfileUrl to fix Instagram social linking When URLs with www. prefix (e.g., www.instagram.com/user) were passed to updateArtistSocials, they wouldn't match existing socials in the database that were stored without the www. prefix. This caused Instagram socials to not be added when the AI provided URLs with www. prefix. The fix normalizes all URLs by stripping the www. prefix, ensuring consistent URL matching regardless of input format. Co-Authored-By: Claude Opus 4.5 --- .../__tests__/updateArtistSocials.test.ts | 85 ++++++++++++++++++- .../__tests__/normalizeProfileUrl.test.ts | 20 +++-- lib/socials/normalizeProfileUrl.ts | 5 +- 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/lib/artist/__tests__/updateArtistSocials.test.ts b/lib/artist/__tests__/updateArtistSocials.test.ts index 6bc8447f..8c5f7b1c 100644 --- a/lib/artist/__tests__/updateArtistSocials.test.ts +++ b/lib/artist/__tests__/updateArtistSocials.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import { updateArtistSocials } from "../updateArtistSocials"; + const mockSelectAccountSocials = vi.fn(); const mockDeleteAccountSocial = vi.fn(); const mockInsertAccountSocial = vi.fn(); @@ -26,8 +28,6 @@ vi.mock("@/lib/supabase/socials/insertSocials", () => ({ insertSocials: (...args: unknown[]) => mockInsertSocials(...args), })); -import { updateArtistSocials } from "../updateArtistSocials"; - describe("updateArtistSocials", () => { const artistId = "artist-123"; @@ -155,7 +155,11 @@ describe("updateArtistSocials", () => { return Promise.resolve([]); }); mockInsertSocials.mockResolvedValue([ - { id: "new-youtube-social", username: "artistchannel", profile_url: "youtube.com/@artistchannel" }, + { + id: "new-youtube-social", + username: "artistchannel", + profile_url: "youtube.com/@artistchannel", + }, ]); // Update both YouTube AND Spotify (same Spotify URL as existing) @@ -171,4 +175,79 @@ describe("updateArtistSocials", () => { // Should also insert YouTube expect(mockInsertAccountSocial).toHaveBeenCalledWith(artistId, "new-youtube-social"); }); + + it("adds both Instagram and Spotify when neither exists", async () => { + // BUG: When passing both Instagram and Spotify URLs for a new artist, + // only one social is being added + mockSelectAccountSocials.mockResolvedValue([]); + mockSelectSocials.mockResolvedValue([]); + + // Mock insertSocials to return different IDs for each call + let insertCallCount = 0; + mockInsertSocials.mockImplementation(socials => { + insertCallCount++; + const social = socials[0]; + if (social.profile_url.includes("instagram")) { + return Promise.resolve([ + { + id: "new-instagram-social", + username: "goosebytheway", + profile_url: social.profile_url, + }, + ]); + } else if (social.profile_url.includes("spotify")) { + return Promise.resolve([ + { id: "new-spotify-social", username: "artist", profile_url: social.profile_url }, + ]); + } + return Promise.resolve([]); + }); + + // Add both Instagram AND Spotify for a new artist + await updateArtistSocials(artistId, { + INSTAGRAM: "https://www.instagram.com/goosebytheway/", + SPOTIFY: "https://open.spotify.com/artist/7Lbpx0EuKekUdtfDuNStfh", + }); + + // Should NOT delete anything since no existing socials + expect(mockDeleteAccountSocial).not.toHaveBeenCalled(); + + // Should create social records for BOTH platforms + expect(mockInsertSocials).toHaveBeenCalledTimes(2); + + // Should create account_social relationships for BOTH platforms + expect(mockInsertAccountSocial).toHaveBeenCalledTimes(2); + expect(mockInsertAccountSocial).toHaveBeenCalledWith(artistId, "new-instagram-social"); + expect(mockInsertAccountSocial).toHaveBeenCalledWith(artistId, "new-spotify-social"); + }); + + it("finds existing Instagram social when www prefix is used in URL", async () => { + // BUG: When URL has www. prefix but database has URL without www., + // the lookup fails and creates a duplicate or fails to link + const existingInstagramInDb = { + id: "existing-instagram-social", + username: "goosebytheway", + profile_url: "instagram.com/goosebytheway", // Note: no www. + }; + + mockSelectAccountSocials.mockResolvedValue([]); + // Simulate database having the URL without www. + mockSelectSocials.mockImplementation(({ profile_url }) => { + // The normalized URL should match even with www. stripped + if (profile_url === "instagram.com/goosebytheway") { + return Promise.resolve([existingInstagramInDb]); + } + return Promise.resolve([]); + }); + + // Input URL has www. prefix + await updateArtistSocials(artistId, { + INSTAGRAM: "https://www.instagram.com/goosebytheway/", + }); + + // Should find existing social and NOT create new one + expect(mockInsertSocials).not.toHaveBeenCalled(); + // Should link to existing social + expect(mockInsertAccountSocial).toHaveBeenCalledWith(artistId, "existing-instagram-social"); + }); }); diff --git a/lib/socials/__tests__/normalizeProfileUrl.test.ts b/lib/socials/__tests__/normalizeProfileUrl.test.ts index bf198ea6..f501e75f 100644 --- a/lib/socials/__tests__/normalizeProfileUrl.test.ts +++ b/lib/socials/__tests__/normalizeProfileUrl.test.ts @@ -13,9 +13,7 @@ describe("normalizeProfileUrl", () => { }); it("returns URL unchanged if no protocol", () => { - expect(normalizeProfileUrl("open.spotify.com/artist/123")).toBe( - "open.spotify.com/artist/123", - ); + expect(normalizeProfileUrl("open.spotify.com/artist/123")).toBe("open.spotify.com/artist/123"); }); it("handles empty string", () => { @@ -28,8 +26,20 @@ describe("normalizeProfileUrl", () => { }); it("removes trailing slash", () => { - expect(normalizeProfileUrl("https://instagram.com/user/")).toBe( - "instagram.com/user", + expect(normalizeProfileUrl("https://instagram.com/user/")).toBe("instagram.com/user"); + }); + + it("removes www. prefix from URL", () => { + expect(normalizeProfileUrl("https://www.instagram.com/user")).toBe("instagram.com/user"); + }); + + it("removes www. prefix along with protocol and trailing slash", () => { + expect(normalizeProfileUrl("https://www.instagram.com/goosebytheway/")).toBe( + "instagram.com/goosebytheway", ); }); + + it("handles URL with www. but without protocol", () => { + expect(normalizeProfileUrl("www.twitter.com/user")).toBe("twitter.com/user"); + }); }); diff --git a/lib/socials/normalizeProfileUrl.ts b/lib/socials/normalizeProfileUrl.ts index 92f348be..2d45a231 100644 --- a/lib/socials/normalizeProfileUrl.ts +++ b/lib/socials/normalizeProfileUrl.ts @@ -1,14 +1,15 @@ /** - * Normalizes a profile URL by removing the protocol and trailing slash. + * Normalizes a profile URL by removing the protocol, www. prefix, and trailing slash. * This ensures consistent URL format for database queries and storage. * * @param url - The profile URL to normalize - * @returns The normalized URL without protocol or trailing slash + * @returns The normalized URL without protocol, www., or trailing slash */ export function normalizeProfileUrl(url: string | null | undefined): string { if (!url) return ""; return url .replace(/^https?:\/\//, "") + .replace(/^www\./, "") .replace(/\/$/, ""); }