Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Id, OptionalId } from '@audius/sdk'
import { QueryClient } from '@tanstack/react-query'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { userCollectionMetadataFromSDK } from '~/adapters/collection'

import { getCollectionByPermalinkQueryFn } from '../useCollectionByPermalink'

vi.mock('~/adapters/collection', () => ({
userCollectionMetadataFromSDK: vi.fn((c) => c)
}))

const makeCollection = (playlist_id: number, permalink: string) =>
({
playlist_id,
permalink,
playlist_contents: { track_ids: [] }
}) as any

describe('getCollectionByPermalinkQueryFn', () => {
let queryClient: QueryClient
let sdk: { playlists: { getBulkPlaylists: ReturnType<typeof vi.fn> } }

beforeEach(() => {
queryClient = new QueryClient()
sdk = { playlists: { getBulkPlaylists: vi.fn() } }
vi.mocked(userCollectionMetadataFromSDK).mockImplementation((c) => c as any)
})

it('returns the playlist id when the permalink lookup succeeds', async () => {
const permalink = '/dj/playlist/summer-mix-100'
sdk.playlists.getBulkPlaylists.mockResolvedValueOnce({
data: [makeCollection(100, permalink)]
})

const result = await getCollectionByPermalinkQueryFn(
permalink,
null,
queryClient,
sdk
)

expect(result).toBe(100)
expect(sdk.playlists.getBulkPlaylists).toHaveBeenCalledTimes(1)
expect(sdk.playlists.getBulkPlaylists).toHaveBeenCalledWith({
permalink: [permalink],
userId: OptionalId.parse(null)
})
})

it('falls back to id lookup when permalink lookup is empty (hidden playlist, logged out)', async () => {
const permalink = '/dj/playlist/hidden-mix-200'
sdk.playlists.getBulkPlaylists
.mockResolvedValueOnce({ data: [] })
.mockResolvedValueOnce({ data: [makeCollection(200, permalink)] })

const result = await getCollectionByPermalinkQueryFn(
permalink,
null,
queryClient,
sdk
)

expect(result).toBe(200)
expect(sdk.playlists.getBulkPlaylists).toHaveBeenCalledTimes(2)
expect(sdk.playlists.getBulkPlaylists).toHaveBeenNthCalledWith(2, {
id: [Id.parse(200)],
userId: OptionalId.parse(null)
})
})

it('rejects an id-fallback result whose permalink does not match (collision guard)', async () => {
const requested = '/dj/playlist/looks-like-300'
const collidingPermalink = '/other/playlist/different-300'
sdk.playlists.getBulkPlaylists
.mockResolvedValueOnce({ data: [] })
.mockResolvedValueOnce({
data: [makeCollection(300, collidingPermalink)]
})

const result = await getCollectionByPermalinkQueryFn(
requested,
null,
queryClient,
sdk
)

expect(result).toBeUndefined()
expect(sdk.playlists.getBulkPlaylists).toHaveBeenCalledTimes(2)
})

it('returns undefined without a second call when the slug has no parseable id', async () => {
const permalink = '/dj/playlist/no-trailing-id'
sdk.playlists.getBulkPlaylists.mockResolvedValueOnce({ data: [] })

const result = await getCollectionByPermalinkQueryFn(
permalink,
null,
queryClient,
sdk
)

expect(result).toBeUndefined()
expect(sdk.playlists.getBulkPlaylists).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { OptionalId } from '@audius/sdk'
import { Id, OptionalId } from '@audius/sdk'
import { useQuery, useQueryClient, QueryClient } from '@tanstack/react-query'
import { pick } from 'lodash'

import { userCollectionMetadataFromSDK } from '~/adapters/collection'
import { useQueryContext } from '~/api/tan-query/utils'
import { ID } from '~/models/Identifiers'
import { parsePlaylistIdFromPermalink } from '~/utils'

import { TQCollection } from '../models'
import { QUERY_KEYS } from '../queryKeys'
Expand All @@ -30,22 +31,41 @@ export const getCollectionByPermalinkQueryFn = async (
queryClient: QueryClient,
sdk: any
) => {
const userId = OptionalId.parse(currentUserId)

const { data = [] } = await sdk.playlists.getBulkPlaylists({
permalink: [permalink],
userId: OptionalId.parse(currentUserId)
userId
})

const collection = userCollectionMetadataFromSDK(data[0])

if (collection) {
// Prime related entities
primeCollectionData({
collections: [collection],
queryClient
})
primeCollectionData({ collections: [collection], queryClient })
return collection.playlist_id
}

// Hidden (is_private) playlists are filtered out of permalink lookups
// for logged-out users, but ID-based lookups honor direct-link access.
// Retry with the id encoded in the permalink slug so anyone with the
// link can view the playlist.
const idFromSlug = parsePlaylistIdFromPermalink(permalink)
if (Number.isNaN(idFromSlug)) return undefined

const { data: byId = [] } = await sdk.playlists.getBulkPlaylists({
id: [Id.parse(idFromSlug)],
userId
})

const byIdCollection = userCollectionMetadataFromSDK(byId[0])
// Guard against id-in-slug collisions: only accept the result if it
// truly maps back to the requested permalink.
if (!byIdCollection || byIdCollection.permalink !== permalink) {
return undefined
}

return collection?.playlist_id
primeCollectionData({ collections: [byIdCollection], queryClient })
return byIdCollection.playlist_id
}

export const useCollectionByPermalink = <TResult = TQCollection>(
Expand Down
Loading