feat: backend implementation for media manager v2 (WIP - Undeployed)
This commit is contained in:
231
src/mediaManager.integration.test.ts
Normal file
231
src/mediaManager.integration.test.ts
Normal file
@ -0,0 +1,231 @@
|
||||
|
||||
import { MediaService } from "./services/MediaService"
|
||||
// Unmock MediaService so we test the real class logic
|
||||
jest.unmock("./services/MediaService")
|
||||
|
||||
// Mock dependencies
|
||||
const mockDrive = {
|
||||
getOrCreateFolder: jest.fn(),
|
||||
getFiles: jest.fn(),
|
||||
createFile: jest.fn(),
|
||||
renameFile: jest.fn(),
|
||||
trashFile: jest.fn(),
|
||||
updateFileProperties: jest.fn(),
|
||||
getFileById: jest.fn()
|
||||
}
|
||||
const mockShopify = {
|
||||
getProductMedia: jest.fn(),
|
||||
productCreateMedia: jest.fn(),
|
||||
productDeleteMedia: jest.fn(),
|
||||
productReorderMedia: jest.fn(),
|
||||
stagedUploadsCreate: jest.fn()
|
||||
}
|
||||
const mockNetwork = { fetch: jest.fn() }
|
||||
const mockConfig = { productPhotosFolderId: "root_folder" }
|
||||
|
||||
// Mock Utilities
|
||||
global.Utilities = {
|
||||
base64Encode: jest.fn().mockReturnValue("base64encoded"),
|
||||
newBlob: jest.fn()
|
||||
} as any
|
||||
|
||||
// Mock Advanced Drive Service
|
||||
global.Drive = {
|
||||
Files: {
|
||||
get: jest.fn().mockImplementation((id) => {
|
||||
if (id === "drive_1") return { appProperties: { shopify_media_id: "gid://shopify/Media/100" } }
|
||||
return { appProperties: {} }
|
||||
})
|
||||
}
|
||||
} as any
|
||||
|
||||
describe("MediaService V2 Integration Logic", () => {
|
||||
let service: MediaService
|
||||
const dummyPid = "gid://shopify/Product/123"
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
// Instantiate the REAL service with MOCKED delegates
|
||||
service = new MediaService(mockDrive as any, mockShopify as any, mockNetwork as any, mockConfig as any)
|
||||
|
||||
// Setup Network mock for Blob download
|
||||
// MediaService calls networkService.fetch(...).getBlob()
|
||||
// so fetch matches MUST return an object with getBlob
|
||||
mockNetwork.fetch.mockReturnValue({
|
||||
getBlob: jest.fn().mockReturnValue({
|
||||
getDataAsString: () => "fake_blob_data",
|
||||
getContentType: () => "image/jpeg",
|
||||
getBytes: () => []
|
||||
})
|
||||
})
|
||||
|
||||
// Setup default File mock behaviors
|
||||
mockDrive.getFileById.mockImplementation((id: string) => ({
|
||||
setName: jest.fn(),
|
||||
getName: () => "file_name.jpg",
|
||||
moveTo: jest.fn(),
|
||||
getMimeType: () => "image/jpeg",
|
||||
getBlob: () => ({})
|
||||
}))
|
||||
|
||||
mockDrive.createFile.mockReturnValue({
|
||||
getId: () => "new_created_file_id"
|
||||
})
|
||||
})
|
||||
|
||||
describe("getUnifiedMediaState (Phase A)", () => {
|
||||
test("should match Drive and Shopify items by ID (Strong Link)", () => {
|
||||
// Setup Drive
|
||||
const driveFile = {
|
||||
getId: () => "drive_1",
|
||||
getName: () => "IMG_001.jpg",
|
||||
getAppProperty: (k: string) => k === 'shopify_media_id' ? "gid://shopify/Media/100" : null,
|
||||
getThumbnail: () => ({ getBytes: () => [] })
|
||||
}
|
||||
mockDrive.getOrCreateFolder.mockReturnValue({ getId: () => "folder_1" })
|
||||
mockDrive.getFiles.mockReturnValue([driveFile])
|
||||
|
||||
// Setup Shopify
|
||||
const shopMedia = {
|
||||
id: "gid://shopify/Media/100",
|
||||
mediaContentType: "IMAGE",
|
||||
preview: { image: { originalSrc: "http://shopify.com/img.jpg" } }
|
||||
}
|
||||
mockShopify.getProductMedia.mockReturnValue([shopMedia])
|
||||
|
||||
// Act
|
||||
const result = service.getUnifiedMediaState("SKU-123", dummyPid)
|
||||
|
||||
// Expect
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].driveId).toBe("drive_1")
|
||||
expect(result[0].shopifyId).toBe("gid://shopify/Media/100")
|
||||
expect(result[0].source).toBe("synced")
|
||||
})
|
||||
|
||||
test("should identify Drive-Only items (New Uploads)", () => {
|
||||
const driveFile = {
|
||||
getId: () => "drive_new",
|
||||
getName: () => "new.jpg",
|
||||
getAppProperty: () => null,
|
||||
getThumbnail: () => ({ getBytes: () => [] })
|
||||
}
|
||||
mockDrive.getOrCreateFolder.mockReturnValue({ getId: () => "folder_1" })
|
||||
mockDrive.getFiles.mockReturnValue([driveFile])
|
||||
mockShopify.getProductMedia.mockReturnValue([])
|
||||
|
||||
const result = service.getUnifiedMediaState("SKU-123", dummyPid)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].source).toBe("drive_only")
|
||||
})
|
||||
|
||||
test("should identify Shopify-Only items", () => {
|
||||
mockDrive.getOrCreateFolder.mockReturnValue({ getId: () => "folder_1" })
|
||||
mockDrive.getFiles.mockReturnValue([])
|
||||
|
||||
const shopMedia = {
|
||||
id: "gid://shopify/Media/555",
|
||||
mediaContentType: "IMAGE",
|
||||
preview: { image: { originalSrc: "url" } }
|
||||
}
|
||||
mockShopify.getProductMedia.mockReturnValue([shopMedia])
|
||||
|
||||
const result = service.getUnifiedMediaState("SKU-123", dummyPid)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].source).toBe("shopify_only")
|
||||
})
|
||||
})
|
||||
|
||||
describe("processMediaChanges (Phase B)", () => {
|
||||
test("should rename Drive files sequentially", () => {
|
||||
const finalState = [
|
||||
{ id: "1", driveId: "d1", shopifyId: "s1", source: "synced", filename: "foo.jpg" },
|
||||
{ id: "2", driveId: "d2", shopifyId: "s2", source: "synced", filename: "bar.jpg" }
|
||||
]
|
||||
|
||||
// Mock getUnifiedMediaState to return empty to skip delete logic interference?
|
||||
// Or return something consistent.
|
||||
jest.spyOn(service, "getUnifiedMediaState").mockReturnValue([])
|
||||
|
||||
// Act
|
||||
service.processMediaChanges("SKU-123", finalState, dummyPid)
|
||||
|
||||
// Assert
|
||||
expect(mockDrive.renameFile).toHaveBeenCalledWith("d1", "SKU-123_0001.jpg")
|
||||
expect(mockDrive.renameFile).toHaveBeenCalledWith("d2", "SKU-123_0002.jpg")
|
||||
})
|
||||
|
||||
test("should call Shopify Reorder Mutation", () => {
|
||||
const finalState = [
|
||||
{ id: "1", shopifyId: "s10", sortOrder: 0 },
|
||||
{ id: "2", shopifyId: "s20", sortOrder: 1 }
|
||||
]
|
||||
jest.spyOn(service, "getUnifiedMediaState").mockReturnValue([])
|
||||
|
||||
service.processMediaChanges("SKU-123", finalState, dummyPid)
|
||||
|
||||
expect(mockShopify.productReorderMedia).toHaveBeenCalledWith(dummyPid, [
|
||||
{ id: "s10", newPosition: "0" },
|
||||
{ id: "s20", newPosition: "1" }
|
||||
])
|
||||
})
|
||||
|
||||
test("should backfill Shopify-Only items to Drive", () => {
|
||||
const finalState = [
|
||||
{ id: "3", driveId: null, shopifyId: "s99", source: "shopify_only", thumbnail: "http://url.jpg", filename: "backfill.jpg" }
|
||||
]
|
||||
jest.spyOn(service, "getUnifiedMediaState").mockReturnValue([])
|
||||
|
||||
// Mock file creation
|
||||
// We set default mockDrive.createFile above but we can specialize if needed
|
||||
// Default returns "new_created_file_id"
|
||||
|
||||
// Act
|
||||
service.processMediaChanges("SKU-123", finalState, dummyPid)
|
||||
|
||||
expect(mockDrive.createFile).toHaveBeenCalled()
|
||||
expect(mockDrive.updateFileProperties).toHaveBeenCalledWith("new_created_file_id", { shopify_media_id: "s99" })
|
||||
})
|
||||
|
||||
test("should delete removed items", () => {
|
||||
// Mock current state has items
|
||||
const current = [
|
||||
{ id: "del_1", driveId: "d_del", shopifyId: "s_del", filename: "delete_me.jpg" }
|
||||
]
|
||||
jest.spyOn(service, "getUnifiedMediaState").mockReturnValue(current)
|
||||
|
||||
// Final state empty
|
||||
const finalState: any[] = []
|
||||
|
||||
service.processMediaChanges("SKU-123", finalState, dummyPid)
|
||||
|
||||
expect(mockShopify.productDeleteMedia).toHaveBeenCalledWith(dummyPid, "s_del")
|
||||
expect(mockDrive.trashFile).toHaveBeenCalledWith("d_del")
|
||||
})
|
||||
|
||||
test("should upload Drive-Only items", () => {
|
||||
const finalState = [
|
||||
{ id: "new_1", driveId: "d_new", shopifyId: null, source: "drive_only", filename: "new.jpg" }
|
||||
]
|
||||
jest.spyOn(service, "getUnifiedMediaState").mockReturnValue([])
|
||||
|
||||
// Mock staged uploads flow
|
||||
mockShopify.stagedUploadsCreate.mockReturnValue({
|
||||
stagedTargets: [{ url: "http://upload", resourceUrl: "http://resource", parameters: [] }]
|
||||
})
|
||||
// Mock Create Media returning ID
|
||||
mockShopify.productCreateMedia.mockReturnValue({
|
||||
media: [{ id: "new_shopify_id", status: "READY" }]
|
||||
})
|
||||
|
||||
service.processMediaChanges("SKU-123", finalState, dummyPid)
|
||||
|
||||
expect(mockShopify.stagedUploadsCreate).toHaveBeenCalled()
|
||||
expect(mockShopify.productCreateMedia).toHaveBeenCalled()
|
||||
// Check property update
|
||||
expect(mockDrive.updateFileProperties).toHaveBeenCalledWith("d_new", { shopify_media_id: "new_shopify_id" })
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user