Skip to content
Draft
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
120 changes: 100 additions & 20 deletions src/publishRelease.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,42 @@ function mockReleaseAssets() {
)
}

function mockTrackUploadResponses(suffix = '1') {
return {
audioUploadResponse: {
id: `audio-upload-${suffix}`,
status: 'done',
orig_file_cid: `orig-audio-cid-${suffix}`,
orig_filename: `audio-${suffix}.flac`,
results: {
320: `track-cid-${suffix}`,
},
audio_analysis_error_count: 0,
},
imageUploadResponse: {
id: `image-upload-${suffix}`,
status: 'done',
orig_file_cid: `image-cid-${suffix}`,
orig_filename: `cover-${suffix}.jpg`,
results: {},
audio_analysis_error_count: 0,
},
}
}

function mockUploadTrackFiles(
...responses: ReturnType<typeof mockTrackUploadResponses>[]
) {
const uploadTrackFilesMock = vi.fn()
for (const response of responses) {
uploadTrackFilesMock.mockReturnValueOnce({
start: vi.fn().mockResolvedValue(response),
abort: vi.fn(),
})
}
return uploadTrackFilesMock
}

function albumRelease(overrides = {}) {
return {
title: 'Album Title',
Expand Down Expand Up @@ -305,7 +341,10 @@ test('publishRelease persists album track ids after each track publish', async (
const plannedTrackId1 = encodeId(101)
const plannedTrackId2 = encodeId(102)
const plannedAlbumId = encodeId(201)
const uploadTrackMock = vi
const trackUpload1 = mockTrackUploadResponses('1')
const trackUpload2 = mockTrackUploadResponses('2')
const uploadTrackFilesMock = mockUploadTrackFiles(trackUpload1, trackUpload2)
const publishTrackMock = vi
.fn()
.mockResolvedValueOnce({
trackId: plannedTrackId1,
Expand All @@ -329,7 +368,8 @@ test('publishRelease persists album track ids after each track publish', async (
const generatePlaylistIdMock = vi.fn().mockResolvedValue(201)
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: generateTrackIdMock,
},
albums: {
Expand All @@ -342,18 +382,23 @@ test('publishRelease persists album track ids after each track publish', async (

await publishRelease(source, releaseRow, albumRelease())

expect(uploadTrackMock).toHaveBeenCalledTimes(2)
expect(uploadTrackMock).toHaveBeenNthCalledWith(
expect(uploadTrackFilesMock).toHaveBeenCalledTimes(2)
expect(publishTrackMock).toHaveBeenCalledTimes(2)
expect(publishTrackMock).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
audioUploadResponse: trackUpload1.audioUploadResponse,
imageUploadResponse: trackUpload1.imageUploadResponse,
metadata: expect.objectContaining({
trackId: plannedTrackId1,
}),
})
)
expect(uploadTrackMock).toHaveBeenNthCalledWith(
expect(publishTrackMock).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
audioUploadResponse: trackUpload2.audioUploadResponse,
imageUploadResponse: trackUpload2.imageUploadResponse,
metadata: expect.objectContaining({
trackId: plannedTrackId2,
}),
Expand Down Expand Up @@ -411,7 +456,8 @@ test('publishRelease persists album track ids after each track publish', async (

test('publishRelease reuses partial album track ids on retry', async () => {
mockReleaseAssets()
const uploadTrackMock = vi.fn()
const uploadTrackFilesMock = vi.fn()
const publishTrackMock = vi.fn()
const generateTrackIdMock = vi.fn()
const generatePlaylistIdMock = vi.fn()
const createAlbumMock = vi.fn().mockResolvedValue({
Expand All @@ -421,7 +467,8 @@ test('publishRelease reuses partial album track ids on retry', async () => {
})
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: generateTrackIdMock,
},
albums: {
Expand All @@ -443,7 +490,8 @@ test('publishRelease reuses partial album track ids on retry', async () => {
albumRelease()
)

expect(uploadTrackMock).not.toHaveBeenCalled()
expect(uploadTrackFilesMock).not.toHaveBeenCalled()
expect(publishTrackMock).not.toHaveBeenCalled()
expect(generateTrackIdMock).not.toHaveBeenCalled()
expect(generatePlaylistIdMock).not.toHaveBeenCalled()
const albumRequest = createAlbumMock.mock.calls[0][0]
Expand All @@ -464,7 +512,11 @@ test('publishRelease reuses partial album track ids on retry', async () => {

test('publishRelease does not mark albums published without a response id', async () => {
mockReleaseAssets()
const uploadTrackMock = vi
const uploadTrackFilesMock = mockUploadTrackFiles(
mockTrackUploadResponses('1'),
mockTrackUploadResponses('2')
)
const publishTrackMock = vi
.fn()
.mockResolvedValueOnce({
trackId: 'track-id-1',
Expand All @@ -482,7 +534,8 @@ test('publishRelease does not mark albums published without a response id', asyn
})
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: vi
.fn()
.mockResolvedValueOnce(101)
Expand Down Expand Up @@ -511,7 +564,11 @@ test('publishRelease keeps partial album track ids when a later track fails', as
mockReleaseAssets()
const plannedTrackId1 = encodeId(101)
const plannedTrackId2 = encodeId(102)
const uploadTrackMock = vi
const uploadTrackFilesMock = mockUploadTrackFiles(
mockTrackUploadResponses('1'),
mockTrackUploadResponses('2')
)
const publishTrackMock = vi
.fn()
.mockResolvedValueOnce({
trackId: plannedTrackId1,
Expand All @@ -526,7 +583,8 @@ test('publishRelease keeps partial album track ids when a later track fails', as
.mockResolvedValueOnce(102)
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: generateTrackIdMock,
},
albums: {
Expand Down Expand Up @@ -561,7 +619,8 @@ test('publishRelease reuses a planned album id when album creation fails', async
const createAlbumMock = vi.fn().mockRejectedValue(new Error('album failed'))
getSdk.mockReturnValue({
tracks: {
createTrack: vi.fn(),
uploadTrackFiles: vi.fn(),
publishTrack: vi.fn(),
generateTrackId: vi.fn(),
},
albums: {
Expand Down Expand Up @@ -604,21 +663,27 @@ test('publishRelease reuses a planned album id when album creation fails', async
test('publishRelease persists a planned single track id before upload', async () => {
mockReleaseAssets()
const plannedTrackId = encodeId(301)
const uploadTrackMock = vi.fn().mockResolvedValue({
const trackUpload = mockTrackUploadResponses('single')
const uploadTrackFilesMock = mockUploadTrackFiles(trackUpload)
const publishTrackMock = vi.fn().mockResolvedValue({
trackId: plannedTrackId,
blockHash: '0xtrack',
blockNumber: 20,
})
const generateTrackIdMock = vi.fn().mockResolvedValue(301)
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: generateTrackIdMock,
},
})

await publishRelease(
source,
{
...source,
placementHosts: 'host-a,host-b',
} as SourceConfig,
{
...releaseRow,
entityId: undefined,
Expand All @@ -645,10 +710,20 @@ test('publishRelease persists a planned single track id before upload', async ()
plannedEntityType: 'track',
plannedEntityId: plannedTrackId,
})
expect(uploadTrackMock).toHaveBeenCalledWith(
expect(uploadTrackFilesMock).toHaveBeenCalledWith(
expect.objectContaining({
fileMetadata: expect.objectContaining({
placementHosts: 'host-a,host-b',
}),
})
)
expect(publishTrackMock).toHaveBeenCalledWith(
expect.objectContaining({
audioUploadResponse: trackUpload.audioUploadResponse,
imageUploadResponse: trackUpload.imageUploadResponse,
metadata: expect.objectContaining({
trackId: plannedTrackId,
placementHosts: 'host-a,host-b',
}),
})
)
Expand All @@ -665,7 +740,9 @@ test('publishRelease persists a planned single track id before upload', async ()
test('publishRelease seeds planned track ids from prior partial album tracks', async () => {
mockReleaseAssets()
const plannedTrackId2 = encodeId(102)
const uploadTrackMock = vi.fn().mockResolvedValue({
const trackUpload = mockTrackUploadResponses('2')
const uploadTrackFilesMock = mockUploadTrackFiles(trackUpload)
const publishTrackMock = vi.fn().mockResolvedValue({
trackId: plannedTrackId2,
blockHash: '0xtrack2',
blockNumber: 11,
Expand All @@ -674,7 +751,8 @@ test('publishRelease seeds planned track ids from prior partial album tracks', a
const generateTrackIdMock = vi.fn().mockResolvedValue(102)
getSdk.mockReturnValue({
tracks: {
createTrack: uploadTrackMock,
uploadTrackFiles: uploadTrackFilesMock,
publishTrack: publishTrackMock,
generateTrackId: generateTrackIdMock,
},
albums: {
Expand All @@ -700,8 +778,10 @@ test('publishRelease seeds planned track ids from prior partial album tracks', a
key: 'release-1',
plannedTrackIds: ['track-id-1', plannedTrackId2],
})
expect(uploadTrackMock).toHaveBeenCalledWith(
expect(publishTrackMock).toHaveBeenCalledWith(
expect.objectContaining({
audioUploadResponse: trackUpload.audioUploadResponse,
imageUploadResponse: trackUpload.imageUploadResponse,
metadata: expect.objectContaining({
trackId: plannedTrackId2,
}),
Expand Down
37 changes: 35 additions & 2 deletions src/publishRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export async function publishRelease(
audioFile: trackFile as any,
}

const result = await sdk.tracks.createTrack(uploadTrackRequest as any)
const result = await publishUploadedTrack(sdk, uploadTrackRequest)
console.log('track published', result)

await publogRepo.log({
Expand Down Expand Up @@ -308,7 +308,7 @@ export async function publishRelease(
async () => encodeId(await sdk.tracks.generateTrackId())
)

const trackResult = await sdk.tracks.createTrack({
const trackResult = await publishUploadedTrack(sdk, {
userId: release.audiusUser!,
metadata: {
...metadata,
Expand Down Expand Up @@ -345,6 +345,39 @@ export async function publishRelease(
}
}

async function publishUploadedTrack(
sdk: ReturnType<typeof getSdk>,
params: UploadTrackRequest
) {
const { audioFile, imageFile, metadata, onProgress, userId } = params
const { audioUploadResponse, imageUploadResponse } =
await sdk.tracks
.uploadTrackFiles({
audioFile,
imageFile,
fileMetadata: {
placementHosts: metadata.placementHosts,
previewStartSeconds: metadata.previewStartSeconds,
},
onProgress,
} as any)
.start()

if (!audioUploadResponse) {
throw new Error('track audio upload response missing')
}
if (!imageUploadResponse) {
throw new Error('track image upload response missing')
}

return sdk.tracks.publishTrack({
userId,
metadata,
audioUploadResponse,
imageUploadResponse,
} as any)
}

async function ensurePlannedEntityId(
releaseRow: ReleaseRow,
entityType: NonNullable<ReleaseRow['plannedEntityType']>,
Expand Down
62 changes: 62 additions & 0 deletions src/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,65 @@ test('SDK v15 accepts the DDEX album create payload shape', async () => {
})
)
})

test('SDK v15 publishTrack preserves uploaded media metadata', async () => {
const manageEntity = vi.fn().mockResolvedValue({
blockHash: '0xblock',
blockNumber: 1,
transactionHash: '0xtx',
})
const audiusSdk = createSdkWithServices({
apiKey: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
apiSecret:
'0x0000000000000000000000000000000000000000000000000000000000000001',
appName: 'ddex-test',
environment: 'production',
services: {
entityManager: { manageEntity } as any,
},
})

const trackId = encodeId(101)
const userId = encodeId(301)

await expect(
audiusSdk.tracks.publishTrack({
userId,
metadata: {
genre: 'Electronic',
title: 'Track',
trackId,
},
audioUploadResponse: {
id: 'audio-upload',
status: 'done',
orig_file_cid: 'orig-audio-cid',
orig_filename: 'audio.mp3',
results: { 320: 'track-cid' },
audio_analysis_error_count: 0,
},
imageUploadResponse: {
id: 'image-upload',
status: 'done',
orig_file_cid: 'cover-cid',
orig_filename: 'cover.jpg',
results: {},
audio_analysis_error_count: 0,
},
} as any)
).resolves.toEqual(
expect.objectContaining({
trackId,
})
)

const metadata = JSON.parse(manageEntity.mock.calls[0][0].metadata)
expect(metadata.data).toEqual(
expect.objectContaining({
cover_art_sizes: 'cover-cid',
orig_file_cid: 'orig-audio-cid',
orig_filename: 'audio.mp3',
track_cid: 'track-cid',
})
)
})