-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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(medusa,types): added store apis for products #7144
Merged
Merged
Changes from 4 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
6979c93
feat(medusa,types): added store apis for products
riqwan 8612399
chore: move files
riqwan d8245ba
Merge branch 'develop' into feat/products-api
riqwan 8e4006f
chore: fix spec
riqwan 49ba1a1
Merge branch 'develop' into feat/products-api
riqwan 3c53057
chore: use context instead of wrap prices helper
riqwan d919b7c
Merge branch 'develop' into feat/products-api
riqwan cafd1cf
chore: cleanup test
riqwan 3d3794c
chore: filter by valid sales channels + publishable keys
riqwan 69f72d0
chore: remove consoles
riqwan a694d9f
chore: fix specs
riqwan 97f42bc
chore: reset store
riqwan d88b089
Merge branch 'develop' into feat/products-api
riqwan aadb2d6
chore: set build
riqwan 765f346
chore: reset
riqwan File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@medusajs/medusa": patch | ||
"@medusajs/types": patch | ||
--- | ||
|
||
feat(medusa,types): added store apis for products |
332 changes: 332 additions & 0 deletions
332
integration-tests/modules/__tests__/product/store/index.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
import { ModuleRegistrationName } from "@medusajs/modules-sdk" | ||
import { | ||
CreateProductDTO, | ||
IPricingModuleService, | ||
IProductModuleService, | ||
ISalesChannelModuleService, | ||
ProductDTO, | ||
ProductVariantDTO, | ||
} from "@medusajs/types" | ||
import { | ||
ContainerRegistrationKeys, | ||
Modules, | ||
ProductStatus, | ||
} from "@medusajs/utils" | ||
import { medusaIntegrationTestRunner } from "medusa-test-utils" | ||
import { createAdminUser } from "../../../../helpers/create-admin-user" | ||
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types" | ||
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" | ||
|
||
jest.setTimeout(50000) | ||
|
||
const env = { MEDUSA_FF_MEDUSA_V2: true } | ||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } } | ||
|
||
async function createProductsWithVariants( | ||
productModule: IProductModuleService, | ||
productsData: CreateProductDTO | ||
): Promise<[ProductDTO, ProductVariantDTO[]]> { | ||
const { variants: variantsData, ...productData } = productsData | ||
|
||
const [product] = await productModule.create([productData]) | ||
riqwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const variantsDataWithProductId = variantsData?.map((variantData) => { | ||
return { ...variantData, product_id: product.id } | ||
}) | ||
|
||
const variants = variantsDataWithProductId | ||
? await productModule.createVariants(variantsDataWithProductId) | ||
: [] | ||
|
||
return [product, variants] | ||
} | ||
|
||
medusaIntegrationTestRunner({ | ||
env, | ||
testSuite: ({ dbConnection, getContainer, api }) => { | ||
describe("Store: Products API", () => { | ||
let appContainer | ||
let product | ||
let product2 | ||
let product3 | ||
let product4 | ||
let variant | ||
let variant2 | ||
let variant3 | ||
let variant4 | ||
let pricingModule: IPricingModuleService | ||
riqwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let productModule: IProductModuleService | ||
let salesChannelModule: ISalesChannelModuleService | ||
|
||
beforeAll(async () => { | ||
appContainer = getContainer() | ||
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING) | ||
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT) | ||
salesChannelModule = appContainer.resolve( | ||
ModuleRegistrationName.SALES_CHANNEL | ||
) | ||
}) | ||
|
||
beforeEach(async () => { | ||
await createAdminUser(dbConnection, adminHeaders, appContainer) | ||
await createDefaultRuleTypes(appContainer) | ||
}) | ||
|
||
describe("GET /store/products", () => { | ||
beforeEach(async () => { | ||
;[product, [variant]] = await createProductsWithVariants( | ||
productModule, | ||
{ | ||
title: "test product 1", | ||
status: ProductStatus.PUBLISHED, | ||
variants: [{ title: "test variant 1" }], | ||
} | ||
) | ||
;[product2, [variant2]] = await createProductsWithVariants( | ||
productModule, | ||
{ | ||
title: "test product 2 uniquely", | ||
status: ProductStatus.PUBLISHED, | ||
variants: [{ title: "test variant 2" }], | ||
} | ||
) | ||
;[product3, [variant3]] = await createProductsWithVariants( | ||
productModule, | ||
{ | ||
title: "product not in price list", | ||
status: ProductStatus.PUBLISHED, | ||
variants: [{ title: "test variant 3" }], | ||
} | ||
) | ||
;[product4, [variant4]] = await createProductsWithVariants( | ||
productModule, | ||
{ | ||
title: "draft product", | ||
status: ProductStatus.DRAFT, | ||
variants: [{ title: "test variant 4" }], | ||
} | ||
) | ||
}) | ||
|
||
it("should list all published products", async () => { | ||
let response = await api.get(`/store/products`) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.count).toEqual(3) | ||
expect(response.data.products).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
id: product.id, | ||
}), | ||
expect.objectContaining({ | ||
id: product2.id, | ||
}), | ||
expect.objectContaining({ | ||
id: product3.id, | ||
}), | ||
]) | ||
) | ||
|
||
response = await api.get(`/store/products?q=uniquely`) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.count).toEqual(1) | ||
expect(response.data.products).toEqual([ | ||
expect.objectContaining({ | ||
id: product2.id, | ||
}), | ||
]) | ||
}) | ||
|
||
it("should list all products for a sales channel", async () => { | ||
const salesChannel = await salesChannelModule.create({ | ||
riqwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
name: "sales channel test", | ||
}) | ||
|
||
const remoteLink = appContainer.resolve( | ||
ContainerRegistrationKeys.REMOTE_LINK | ||
) | ||
|
||
await remoteLink.create([ | ||
{ | ||
[Modules.PRODUCT]: { product_id: product.id }, | ||
[Modules.SALES_CHANNEL]: { sales_channel_id: salesChannel.id }, | ||
}, | ||
]) | ||
|
||
let response = await api.get( | ||
`/store/products?sales_channel_id[]=${salesChannel.id}` | ||
) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.count).toEqual(1) | ||
expect(response.data.products).toEqual([ | ||
expect.objectContaining({ | ||
id: product.id, | ||
}), | ||
]) | ||
}) | ||
|
||
it("should throw error when calculating prices without context", async () => { | ||
let error = await api | ||
.get(`/store/products?fields=*variants.prices`) | ||
.catch((e) => e) | ||
|
||
expect(error.response.status).toEqual(400) | ||
expect(error.response.data).toEqual({ | ||
message: | ||
"Pricing parameters (currency_code or region_id) are required to calculate prices", | ||
type: "invalid_data", | ||
}) | ||
}) | ||
|
||
it("should list products with prices when context is present", async () => { | ||
await createVariantPriceSet({ | ||
container: appContainer, | ||
variantId: variant.id, | ||
prices: [{ amount: 3000, currency_code: "usd" }], | ||
rules: [], | ||
}) | ||
|
||
let response = await api.get( | ||
`/store/products?fields=*variants.prices¤cy_code=usd` | ||
) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.count).toEqual(3) | ||
expect(response.data.products).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
id: product.id, | ||
variants: [ | ||
expect.objectContaining({ | ||
price: { | ||
id: expect.any(String), | ||
is_calculated_price_price_list: false, | ||
calculated_amount: 3000, | ||
is_original_price_price_list: false, | ||
original_amount: 3000, | ||
currency_code: "usd", | ||
calculated_price: { | ||
id: expect.any(String), | ||
price_list_id: null, | ||
price_list_type: null, | ||
min_quantity: null, | ||
max_quantity: null, | ||
}, | ||
original_price: { | ||
id: expect.any(String), | ||
price_list_id: null, | ||
price_list_type: null, | ||
min_quantity: null, | ||
max_quantity: null, | ||
}, | ||
}, | ||
}), | ||
], | ||
}), | ||
expect.objectContaining({ | ||
id: product2.id, | ||
variants: [ | ||
expect.objectContaining({ | ||
price: null, | ||
}), | ||
], | ||
}), | ||
expect.objectContaining({ | ||
id: product3.id, | ||
}), | ||
]) | ||
) | ||
}) | ||
}) | ||
|
||
describe("GET /store/products/:id", () => { | ||
beforeEach(async () => { | ||
;[product, [variant]] = await createProductsWithVariants( | ||
productModule, | ||
{ | ||
title: "test product 1", | ||
status: ProductStatus.PUBLISHED, | ||
variants: [{ title: "test variant 1" }], | ||
} | ||
) | ||
}) | ||
|
||
it("should retrieve product successfully", async () => { | ||
let response = await api.get(`/store/products/${product.id}`) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.product).toEqual( | ||
expect.objectContaining({ | ||
id: product.id, | ||
variants: [ | ||
expect.objectContaining({ | ||
id: expect.any(String), | ||
}), | ||
], | ||
}) | ||
) | ||
}) | ||
|
||
it("should throw error when calculating prices without context", async () => { | ||
let error = await api | ||
.get(`/store/products/${product.id}?fields=*variants.prices`) | ||
.catch((e) => e) | ||
|
||
expect(error.response.status).toEqual(400) | ||
expect(error.response.data).toEqual({ | ||
message: | ||
"Pricing parameters (currency_code or region_id) are required to calculate prices", | ||
type: "invalid_data", | ||
}) | ||
}) | ||
|
||
it("should get product with prices when context is present", async () => { | ||
await createVariantPriceSet({ | ||
riqwan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
container: appContainer, | ||
variantId: variant.id, | ||
prices: [{ amount: 3000, currency_code: "usd" }], | ||
rules: [], | ||
}) | ||
|
||
let response = await api.get( | ||
`/store/products/${product.id}?fields=*variants.prices¤cy_code=usd` | ||
) | ||
|
||
expect(response.status).toEqual(200) | ||
expect(response.data.product).toEqual( | ||
expect.objectContaining({ | ||
id: product.id, | ||
variants: [ | ||
expect.objectContaining({ | ||
price: { | ||
id: expect.any(String), | ||
is_calculated_price_price_list: false, | ||
calculated_amount: 3000, | ||
is_original_price_price_list: false, | ||
original_amount: 3000, | ||
currency_code: "usd", | ||
calculated_price: { | ||
id: expect.any(String), | ||
price_list_id: null, | ||
price_list_type: null, | ||
min_quantity: null, | ||
max_quantity: null, | ||
}, | ||
original_price: { | ||
id: expect.any(String), | ||
price_list_id: null, | ||
price_list_type: null, | ||
min_quantity: null, | ||
max_quantity: null, | ||
}, | ||
}, | ||
}), | ||
], | ||
}) | ||
) | ||
}) | ||
}) | ||
}) | ||
}, | ||
}) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@riqwan I know you wrote a lot of tests here, but does it make sense to actually make the
integration-tests/api
tests work instead? If you feel like it will take a lot of time I'm fine with doing it later on, but we really need to try and clean up the testsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 points:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@riqwan The idea is that, unless we intended a breaking change, the test scenarios in
api
are still valid and should work regardless if it is v1 or v2. Eg. for the product endpoints, we have very few breaking changes, and that gives us some confidence that we are compliant with v1, except for the intentional breaking changes.But if you feel like that is not a valid point, then we should discuss and settle on a single approach, as currently we have a split between the two