Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add product export endpoint and a dummy workflow #8178

Merged
merged 1 commit into from
Jul 18, 2024
Merged
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
453 changes: 0 additions & 453 deletions integration-tests/api/__tests__/batch-jobs/product/export.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,398 @@
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { getProductFixture } from "../../../../helpers/fixtures"

jest.setTimeout(50000)

medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let baseProduct
let proposedProduct

let baseCollection
let publishedCollection

let baseType

beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, getContainer())

baseCollection = (
await api.post(
"/admin/collections",
{ title: "base-collection" },
adminHeaders
)
).data.collection

publishedCollection = (
await api.post(
"/admin/collections",
{ title: "proposed-collection" },
adminHeaders
)
).data.collection

baseType = (
await api.post(
"/admin/product-types",
{ value: "test-type" },
adminHeaders
)
).data.product_type

baseProduct = (
await api.post(
"/admin/products",
getProductFixture({
title: "Base product",
collection_id: baseCollection.id,
type_id: baseType.id,
}),
adminHeaders
)
).data.product

proposedProduct = (
await api.post(
"/admin/products",
getProductFixture({
title: "Proposed product",
status: "proposed",
tags: [{ value: "new-tag" }],
type_id: baseType.id,
}),
adminHeaders
)
).data.product
})

describe("POST /admin/products/export", () => {
it("should export a csv file containing the expected products", async () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests will be enabled once the workflow is implemented (follow-up PRs)

// BREAKING: The batch endpoints moved to the domain routes (admin/batch-jobs -> /admin/products/export). The payload and response changed as well.
const batchJobRes = await api.post(
"/admin/products/export",
{},
adminHeaders
)

const workflowId = batchJobRes.data.workflow_id
expect(workflowId).toBeTruthy()

// Pull to check the status until it is completed
while (true) {
Copy link
Contributor

@carlos-r-l-rodrigues carlos-r-l-rodrigues Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment from someone that is not aware of all the underlying mechanism and not only valid for the test here:
This looks a good use case for the workflow engine and the async workflow triggers the action and we subscribe to that transaction to handle when it is done.
Even to push these events to the UI would be great.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agreed, I just copy-pasted the tests from the original batch tests, and I will be changing them as I am implementing the exports. I'll try using what you suggested instead of the while loop 👍

// const res = await api.get(
// `/admin/batch-jobs/${batchJobId}`,
// adminReqConfig
// )
// await new Promise((resolve, _) => {
// setTimeout(resolve, 1000)
// })
// batchJob = res.data.batch_job
// shouldContinuePulling = !(
// batchJob.status === "completed" || batchJob.status === "failed"
// )
break
}

// expect(batchJob.status).toBe("completed")

// exportFilePath = path.resolve(__dirname, batchJob.result.file_key)
// const isFileExists = (await fs.stat(exportFilePath)).isFile()

// expect(isFileExists).toBeTruthy()

// const fileSize = (await fs.stat(exportFilePath)).size
// expect(batchJob.result?.file_size).toBe(fileSize)

// const data = (await fs.readFile(exportFilePath)).toString()
// const [, ...lines] = data.split("\r\n").filter((l) => l)

// expect(lines.length).toBe(1)

// const lineColumn = lines[0].split(";")

// expect(lineColumn[0]).toBe(productId)
// expect(lineColumn[2]).toBe(productPayload.title)
// expect(lineColumn[4]).toBe(productPayload.description)
// expect(lineColumn[23]).toBe(variantId)
// expect(lineColumn[24]).toBe(productPayload.variants[0].title)
// expect(lineColumn[25]).toBe(productPayload.variants[0].sku)
})

// it("should export a csv file containing the expected products including new line char in the cells", async () => {
// const api = useApi()

// const productPayload = {
// title: "Test export product",
// description: "test-product-description\ntest line 2",
// type: { value: "test-type" },
// images: ["test-image.png", "test-image-2.png"],
// collection_id: "test-collection",
// tags: [{ value: "123" }, { value: "456" }],
// options: [{ title: "size" }, { title: "color" }],
// variants: [
// {
// title: "Test variant",
// inventory_quantity: 10,
// sku: "test-variant-sku-product-export",
// prices: [
// {
// currency_code: "usd",
// amount: 100,
// },
// {
// currency_code: "eur",
// amount: 45,
// },
// {
// currency_code: "dkk",
// amount: 30,
// },
// ],
// options: [{ value: "large" }, { value: "green" }],
// },
// ],
// }
// const createProductRes = await api.post(
// "/admin/products",
// productPayload,
// adminReqConfig
// )
// const productId = createProductRes.data.product.id
// const variantId = createProductRes.data.product.variants[0].id

// const batchPayload = {
// type: "product-export",
// context: {
// filterable_fields: {
// title: "Test export product",
// },
// },
// }
// const batchJobRes = await api.post(
// "/admin/batch-jobs",
// batchPayload,
// adminReqConfig
// )
// const batchJobId = batchJobRes.data.batch_job.id

// expect(batchJobId).toBeTruthy()

// // Pull to check the status until it is completed
// let batchJob
// let shouldContinuePulling = true
// while (shouldContinuePulling) {
// const res = await api.get(
// `/admin/batch-jobs/${batchJobId}`,
// adminReqConfig
// )

// await new Promise((resolve, _) => {
// setTimeout(resolve, 1000)
// })

// batchJob = res.data.batch_job
// shouldContinuePulling = !(
// batchJob.status === "completed" || batchJob.status === "failed"
// )
// }

// expect(batchJob.status).toBe("completed")

// exportFilePath = path.resolve(__dirname, batchJob.result.file_key)
// const isFileExists = (await fs.stat(exportFilePath)).isFile()

// expect(isFileExists).toBeTruthy()

// const fileSize = (await fs.stat(exportFilePath)).size
// expect(batchJob.result?.file_size).toBe(fileSize)

// const data = (await fs.readFile(exportFilePath)).toString()
// const [, ...lines] = data.split("\r\n").filter((l) => l)

// expect(lines.length).toBe(1)

// const lineColumn = lines[0].split(";")

// expect(lineColumn[0]).toBe(productId)
// expect(lineColumn[2]).toBe(productPayload.title)
// expect(lineColumn[4]).toBe(`"${productPayload.description}"`)
// expect(lineColumn[23]).toBe(variantId)
// expect(lineColumn[24]).toBe(productPayload.variants[0].title)
// expect(lineColumn[25]).toBe(productPayload.variants[0].sku)
// })

// it("should export a csv file containing a limited number of products", async () => {
// const api = useApi()

// const batchPayload = {
// type: "product-export",
// context: {
// batch_size: 1,
// filterable_fields: { collection_id: "test-collection" },
// order: "created_at",
// },
// }

// const batchJobRes = await api.post(
// "/admin/batch-jobs",
// batchPayload,
// adminReqConfig
// )
// const batchJobId = batchJobRes.data.batch_job.id

// expect(batchJobId).toBeTruthy()

// // Pull to check the status until it is completed
// let batchJob
// let shouldContinuePulling = true
// while (shouldContinuePulling) {
// const res = await api.get(
// `/admin/batch-jobs/${batchJobId}`,
// adminReqConfig
// )

// await new Promise((resolve, _) => {
// setTimeout(resolve, 1000)
// })

// batchJob = res.data.batch_job
// shouldContinuePulling = !(
// batchJob.status === "completed" || batchJob.status === "failed"
// )
// }

// expect(batchJob.status).toBe("completed")

// exportFilePath = path.resolve(__dirname, batchJob.result.file_key)
// const isFileExists = (await fs.stat(exportFilePath)).isFile()

// expect(isFileExists).toBeTruthy()

// const data = (await fs.readFile(exportFilePath)).toString()
// const [, ...lines] = data.split("\r\n").filter((l) => l)

// expect(lines.length).toBe(4)

// const csvLine = lines[0].split(";")
// expect(csvLine[0]).toBe("test-product")
// })

// it("should be able to import an exported csv file", async () => {
// const api = useApi()

// const batchPayload = {
// type: "product-export",
// context: {
// batch_size: 1,
// filterable_fields: { collection_id: "test-collection" },
// order: "created_at",
// },
// }

// const batchJobRes = await api.post(
// "/admin/batch-jobs",
// batchPayload,
// adminReqConfig
// )
// let batchJobId = batchJobRes.data.batch_job.id

// expect(batchJobId).toBeTruthy()

// // Pull to check the status until it is completed
// let batchJob
// let shouldContinuePulling = true
// while (shouldContinuePulling) {
// const res = await api.get(
// `/admin/batch-jobs/${batchJobId}`,
// adminReqConfig
// )

// await new Promise((resolve, _) => {
// setTimeout(resolve, 1000)
// })

// batchJob = res.data.batch_job
// shouldContinuePulling = !(
// batchJob.status === "completed" || batchJob.status === "failed"
// )
// }

// expect(batchJob.status).toBe("completed")

// exportFilePath = path.resolve(__dirname, batchJob.result.file_key)
// const isFileExists = (await fs.stat(exportFilePath)).isFile()

// expect(isFileExists).toBeTruthy()

// const data = (await fs.readFile(exportFilePath)).toString()
// const [header, ...lines] = data.split("\r\n").filter((l) => l)

// expect(lines.length).toBe(4)

// const csvLine = lines[0].split(";")
// expect(csvLine[0]).toBe("test-product")
// expect(csvLine[2]).toBe("Test product")

// csvLine[2] = "Updated test product"
// lines.splice(0, 1, csvLine.join(";"))

// await fs.writeFile(exportFilePath, [header, ...lines].join("\r\n"))

// const importBatchJobRes = await api.post(
// "/admin/batch-jobs",
// {
// type: "product-import",
// context: {
// fileKey: exportFilePath,
// },
// },
// adminReqConfig
// )

// batchJobId = importBatchJobRes.data.batch_job.id

// expect(batchJobId).toBeTruthy()

// shouldContinuePulling = true
// while (shouldContinuePulling) {
// const res = await api.get(
// `/admin/batch-jobs/${batchJobId}`,
// adminReqConfig
// )

// await new Promise((resolve, _) => {
// setTimeout(resolve, 1000)
// })

// batchJob = res.data.batch_job

// shouldContinuePulling = !(
// batchJob.status === "completed" || batchJob.status === "failed"
// )
// }

// expect(batchJob.status).toBe("completed")

// const productsResponse = await api.get(
// "/admin/products",
// adminReqConfig
// )
// expect(productsResponse.data.count).toBe(5)
// expect(productsResponse.data.products).toEqual(
// expect.arrayContaining([
// expect.objectContaining({
// id: csvLine[0],
// handle: csvLine[1],
// title: csvLine[2],
// }),
// ])
// )
// })
})
},
})
Loading
Loading