Fix Drive video upload to Shopify
- Detect video mime types in MediaService to set correct resource ('VIDEO') and mediaContentType.
- Add fileSize to stagedUploadsCreate payload as required by Shopify for videos.
- Add safety check for missing upload targets to prevent crashes.
- Implement getSize in MockDriveService.
- Add unit test in MediaService.test.ts to verify correct resource and fileSize handling for video uploads.
- Update mock in mediaManager.integration.test.ts to support getSize().
This commit is contained in:
@ -72,6 +72,7 @@ describe("MediaService V2 Integration Logic", () => {
|
|||||||
moveTo: jest.fn(),
|
moveTo: jest.fn(),
|
||||||
getMimeType: () => "image/jpeg",
|
getMimeType: () => "image/jpeg",
|
||||||
getBlob: () => ({}),
|
getBlob: () => ({}),
|
||||||
|
getSize: () => 1024,
|
||||||
getId: () => id
|
getId: () => id
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@ -163,4 +163,46 @@ describe("MediaService Robust Sync", () => {
|
|||||||
expect(spyRename).toHaveBeenCalledWith(f1.getId(), expect.stringMatching(/^SKU123_\d+\.jpg$/))
|
expect(spyRename).toHaveBeenCalledWith(f1.getId(), expect.stringMatching(/^SKU123_\d+\.jpg$/))
|
||||||
expect(spyRename).not.toHaveBeenCalledWith(f2.getId(), expect.anything())
|
expect(spyRename).not.toHaveBeenCalledWith(f2.getId(), expect.anything())
|
||||||
})
|
})
|
||||||
|
test("Upload: Handles Video Uploads with correct resource type", () => {
|
||||||
|
const folder = driveService.getOrCreateFolder("SKU_VIDEO", "root")
|
||||||
|
|
||||||
|
// Mock Video Blob
|
||||||
|
const videoBlob = {
|
||||||
|
getName: () => "video.mp4",
|
||||||
|
getBytes: () => [],
|
||||||
|
getContentType: () => "video/mp4",
|
||||||
|
getThumbnail: () => ({ getBytes: () => [] })
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const vidFile = driveService.saveFile(videoBlob, folder.getId())
|
||||||
|
|
||||||
|
const finalState = [{
|
||||||
|
id: vidFile.getId(),
|
||||||
|
driveId: vidFile.getId(),
|
||||||
|
filename: "video.mp4",
|
||||||
|
source: "drive_only"
|
||||||
|
}]
|
||||||
|
|
||||||
|
const spyStaged = jest.spyOn(shopifyService, 'stagedUploadsCreate')
|
||||||
|
const spyCreate = jest.spyOn(shopifyService, 'productCreateMedia')
|
||||||
|
|
||||||
|
mediaService.processMediaChanges("SKU_VIDEO", finalState, "pid")
|
||||||
|
|
||||||
|
// 1. Verify stagedUploadsCreate called with resource="VIDEO" and fileSize
|
||||||
|
expect(spyStaged).toHaveBeenCalledWith(expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
resource: "VIDEO",
|
||||||
|
mimeType: "video/mp4",
|
||||||
|
filename: "video.mp4",
|
||||||
|
fileSize: "0" // 0 because mock bytes are empty
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
|
||||||
|
// 2. Verify productCreateMedia called with mediaContentType="VIDEO"
|
||||||
|
expect(spyCreate).toHaveBeenCalledWith("pid", expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
mediaContentType: "VIDEO"
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -277,7 +277,8 @@ export class MediaService {
|
|||||||
return {
|
return {
|
||||||
filename: f.getName(),
|
filename: f.getName(),
|
||||||
mimeType: f.getMimeType(),
|
mimeType: f.getMimeType(),
|
||||||
resource: "IMAGE",
|
resource: f.getMimeType().startsWith('video/') ? "VIDEO" : "IMAGE",
|
||||||
|
fileSize: f.getSize().toString(),
|
||||||
httpMethod: "POST",
|
httpMethod: "POST",
|
||||||
file: f,
|
file: f,
|
||||||
originalItem: item
|
originalItem: item
|
||||||
@ -290,14 +291,26 @@ export class MediaService {
|
|||||||
filename: u.filename,
|
filename: u.filename,
|
||||||
mimeType: u.mimeType,
|
mimeType: u.mimeType,
|
||||||
resource: u.resource,
|
resource: u.resource,
|
||||||
|
fileSize: u.fileSize,
|
||||||
httpMethod: u.httpMethod
|
httpMethod: u.httpMethod
|
||||||
}))
|
}))
|
||||||
const stagedResp = shopifySvc.stagedUploadsCreate(stagedInput)
|
const stagedResp = shopifySvc.stagedUploadsCreate(stagedInput)
|
||||||
|
|
||||||
|
if (stagedResp.userErrors && stagedResp.userErrors.length > 0) {
|
||||||
|
console.error("[MediaService] stagedUploadsCreate Errors:", JSON.stringify(stagedResp.userErrors))
|
||||||
|
logs.push(`- Upload preparation failed: ${stagedResp.userErrors.map(e => e.message).join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
const targets = stagedResp.stagedTargets
|
const targets = stagedResp.stagedTargets
|
||||||
|
|
||||||
const mediaToCreate = []
|
const mediaToCreate = []
|
||||||
uploads.forEach((u, i) => {
|
uploads.forEach((u, i) => {
|
||||||
const target = targets[i]
|
const target = targets[i]
|
||||||
|
if (!target || !target.url) {
|
||||||
|
logs.push(`- Failed to get upload target for ${u.filename}: Invalid target`)
|
||||||
|
console.warn(`[MediaService] Missing target URL for ${u.filename}. Target:`, JSON.stringify(target))
|
||||||
|
return
|
||||||
|
}
|
||||||
const payload = {}
|
const payload = {}
|
||||||
target.parameters.forEach((p: any) => payload[p.name] = p.value)
|
target.parameters.forEach((p: any) => payload[p.name] = p.value)
|
||||||
payload['file'] = u.file.getBlob()
|
payload['file'] = u.file.getBlob()
|
||||||
@ -305,7 +318,7 @@ export class MediaService {
|
|||||||
mediaToCreate.push({
|
mediaToCreate.push({
|
||||||
originalSource: target.resourceUrl,
|
originalSource: target.resourceUrl,
|
||||||
alt: u.filename,
|
alt: u.filename,
|
||||||
mediaContentType: "IMAGE"
|
mediaContentType: u.resource
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export class MockDriveService implements IDriveService {
|
|||||||
getThumbnail: () => ({ getBytes: () => [] }),
|
getThumbnail: () => ({ getBytes: () => [] }),
|
||||||
getMimeType: () => (blob as any).getContentType ? (blob as any).getContentType() : "image/jpeg",
|
getMimeType: () => (blob as any).getContentType ? (blob as any).getContentType() : "image/jpeg",
|
||||||
getDownloadUrl: () => `https://drive.google.com/uc?export=download&id=${id}`,
|
getDownloadUrl: () => `https://drive.google.com/uc?export=download&id=${id}`,
|
||||||
|
getSize: () => blob.getBytes ? blob.getBytes().length : 0,
|
||||||
getAppProperty: (key) => {
|
getAppProperty: (key) => {
|
||||||
return (newFile as any)._properties?.[key]
|
return (newFile as any)._properties?.[key]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user