-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(medusa,product,core-flows,types): product options redesign (server-side) #13817
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
Open
willbouch
wants to merge
45
commits into
develop
Choose a base branch
from
chore/add-many-to-many-between-product-and-option
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
b7d9de8
chore(product,types): change relationship between product and option …
willbouch f7de05e
tests
willbouch 2210c08
define model for pivot table
willbouch cb3dda5
test commit will revert
willbouch 4a62432
Revert "test commit will revert"
willbouch 8fc85ca
model for pivot table
willbouch a7cbbfe
more
willbouch af7b374
new product options endpoint
willbouch c0af764
product options endpoints
willbouch b4020d6
tests
willbouch cdd2dec
remove debug
willbouch 5f064d4
small changes in endpoints
willbouch 14e4778
add ranks to product option values
willbouch 60b3430
allow linking to existing option on product creation
willbouch a97c04e
updates to link endpoint
willbouch 6830b54
merge conflicts
willbouch f89f19e
Create forty-tables-fetch.md
willbouch f32e5d3
integration tests
willbouch 72245db
integration tests
willbouch c35bcd2
product integration tests
willbouch a0b7ea9
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch deff5cc
update validator
willbouch c914fc1
fix import
willbouch 28ba1d2
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch 4e0d98b
fix import
willbouch afb4455
maintain ordering
willbouch 454173f
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch 5ffe5f6
self review
willbouch 2c58173
pr comments
willbouch 3a9d74e
pr comments
willbouch 39289af
pr comments
willbouch 75662f4
is string
willbouch 1eeb65d
pr comments
willbouch 046d767
query graph
willbouch df36394
comment about moving error
willbouch a576bb2
create options in parallel
willbouch a9d7985
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
olivermrbl a7fc306
comments
willbouch c874344
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch d1aa1c1
small change in doc
willbouch 50b44c8
migration name
willbouch 6d20893
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch 1c044f6
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch ddd4f36
fix test
willbouch 8041f0e
Merge branch 'develop' into chore/add-many-to-many-between-product-an…
willbouch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,8 @@ | ||
| --- | ||
| "@medusajs/medusa": minor | ||
| "@medusajs/product": minor | ||
| "@medusajs/core-flows": minor | ||
| "@medusajs/types": minor | ||
| --- | ||
|
|
||
| feat(medusa,product,core-flows,types): product options redesign (server-side) |
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -2,4 +2,4 @@ | |
| DB_HOST=localhost | ||
| DB_USERNAME=postgres | ||
| DB_PASSWORD='' | ||
| LOG_LEVEL=error | ||
| LOG_LEVEL=error | ||
274 changes: 274 additions & 0 deletions
274
integration-tests/http/__tests__/product-option/product-option.spec.ts
This file contains hidden or 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,274 @@ | ||
| import { medusaIntegrationTestRunner } from "@medusajs/test-utils" | ||
| import { | ||
| adminHeaders, | ||
| createAdminUser, | ||
| } from "../../../helpers/create-admin-user" | ||
|
|
||
| jest.setTimeout(30000) | ||
|
|
||
| medusaIntegrationTestRunner({ | ||
| env: {}, | ||
| testSuite: ({ dbConnection, getContainer, api }) => { | ||
| let option1 | ||
| let option2 | ||
|
|
||
| beforeEach(async () => { | ||
| const container = getContainer() | ||
| await createAdminUser(dbConnection, adminHeaders, container) | ||
|
|
||
| option1 = ( | ||
| await api.post( | ||
| "/admin/product-options", | ||
| { | ||
| title: "option1", | ||
| values: ["A", "B", "C"], | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| ).data.product_option | ||
|
|
||
| option2 = ( | ||
| await api.post( | ||
| "/admin/product-options", | ||
| { | ||
| title: "option2", | ||
| values: ["D", "E"], | ||
| is_exclusive: true, | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| ).data.product_option | ||
| }) | ||
|
|
||
| describe("GET /admin/product-options", () => { | ||
| it("should return a list of product options", async () => { | ||
| const res = await api.get("/admin/product-options", adminHeaders) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_options).toEqual( | ||
| expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| title: "option1", | ||
| is_exclusive: false, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ value: "A" }), | ||
| expect.objectContaining({ value: "B" }), | ||
| expect.objectContaining({ value: "C" }), | ||
| ]), | ||
| }), | ||
| expect.objectContaining({ | ||
| title: "option2", | ||
| is_exclusive: true, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ value: "D" }), | ||
| expect.objectContaining({ value: "E" }), | ||
| ]), | ||
| }), | ||
| ]) | ||
| ) | ||
| }) | ||
|
|
||
| it("should return a list of product options matching free text search param", async () => { | ||
| const res = await api.get("/admin/product-options?q=1", adminHeaders) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_options.length).toEqual(1) | ||
| expect(res.data.product_options).toEqual( | ||
| expect.arrayContaining([ | ||
| expect.objectContaining({ title: "option1" }), | ||
| ]) | ||
| ) | ||
| }) | ||
|
|
||
| it("should return a list of exclusive product options", async () => { | ||
| const res = await api.get( | ||
| "/admin/product-options?is_exclusive=false", | ||
| adminHeaders | ||
| ) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_options.length).toEqual(1) | ||
| expect(res.data.product_options).toEqual( | ||
| expect.arrayContaining([ | ||
| expect.objectContaining({ title: "option1" }), | ||
| ]) | ||
| ) | ||
| }) | ||
| }) | ||
|
|
||
| describe("POST /admin/product-options", () => { | ||
| it("should create a product option with value ranks", async () => { | ||
| const option = ( | ||
| await api.post( | ||
| `/admin/product-options`, | ||
| { | ||
| title: "option3", | ||
| values: ["D", "E"], | ||
| ranks: { | ||
| E: 1, | ||
| D: 2, | ||
| }, | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| ).data.product_option | ||
|
|
||
| expect(option).toEqual( | ||
| expect.objectContaining({ | ||
| title: "option3", | ||
| is_exclusive: false, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| value: "D", | ||
| rank: 2, | ||
| }), | ||
| expect.objectContaining({ | ||
| value: "E", | ||
| rank: 1, | ||
| }), | ||
| ]), | ||
| }) | ||
| ) | ||
| }) | ||
|
|
||
| it("should throw if a rank is specified for invalid value", async () => { | ||
| const error = await api | ||
| .post( | ||
| `/admin/product-options`, | ||
| { | ||
| title: "option3", | ||
| values: ["D", "E"], | ||
| ranks: { | ||
| E: 1, | ||
| invalid: 2, | ||
| }, | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| .catch((err) => err) | ||
|
|
||
| expect(error.response.status).toEqual(400) | ||
| expect(error.response.data.message).toEqual( | ||
| 'Value "invalid" is assigned a rank but is not defined in the list of values.' | ||
| ) | ||
| }) | ||
| }) | ||
|
|
||
| describe("GET /admin/product-options/[id]", () => { | ||
| it("should return a product option", async () => { | ||
| const res = await api.get( | ||
| `/admin/product-options/${option1.id}`, | ||
| adminHeaders | ||
| ) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_option.values.length).toEqual(3) | ||
| expect(res.data.product_option).toEqual( | ||
willbouch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| expect.objectContaining({ | ||
| title: "option1", | ||
| is_exclusive: false, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ value: "A" }), | ||
| expect.objectContaining({ value: "B" }), | ||
| expect.objectContaining({ value: "C" }), | ||
| ]), | ||
| }) | ||
| ) | ||
| }) | ||
| }) | ||
|
|
||
| describe("POST /admin/product-options/[id]", () => { | ||
| it("should update a product option", async () => { | ||
| const option = ( | ||
| await api.post( | ||
| `/admin/product-options/${option2.id}`, | ||
| { | ||
| is_exclusive: false, | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| ).data.product_option | ||
|
|
||
| expect(option.values.length).toEqual(2) | ||
| expect(option).toEqual( | ||
| expect.objectContaining({ | ||
| title: "option2", | ||
| is_exclusive: false, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ value: "D" }), | ||
| expect.objectContaining({ value: "E" }), | ||
| ]), | ||
| }) | ||
| ) | ||
|
|
||
| const res = await api.get( | ||
| "/admin/product-options?is_exclusive=true", | ||
| adminHeaders | ||
| ) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_options.length).toEqual(0) | ||
| }) | ||
|
|
||
| it("should update a product value ranks", async () => { | ||
| const option = ( | ||
| await api.post( | ||
| `/admin/product-options/${option2.id}`, | ||
| { | ||
| ranks: { | ||
| D: 2, | ||
| E: 1, | ||
| }, | ||
| }, | ||
| adminHeaders | ||
| ) | ||
| ).data.product_option | ||
|
|
||
| expect(option.values.length).toEqual(2) | ||
| expect(option).toEqual( | ||
| expect.objectContaining({ | ||
| title: "option2", | ||
| is_exclusive: true, | ||
| values: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| value: "D", | ||
| rank: 2, | ||
| }), | ||
| expect.objectContaining({ | ||
| value: "E", | ||
| rank: 1, | ||
| }), | ||
| ]), | ||
| }) | ||
| ) | ||
| }) | ||
|
|
||
| it("should throw when trying to update an option that does not exist", async () => { | ||
| const error = await api.post( | ||
| `/admin/product-options/iDontExist`, | ||
| { | ||
| is_exclusive: false, | ||
| }, | ||
| adminHeaders | ||
| ).catch((e) => e) | ||
|
|
||
| expect(error.response.status).toEqual(404) | ||
| expect(error.response.data).toEqual({ | ||
| message: "Product option with id \"iDontExist\" not found", | ||
| type: "not_found" | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| describe("DELETE /admin/product-options/[id]", () => { | ||
| it("should delete a product option", async () => { | ||
| await api.delete(`/admin/product-options/${option2.id}`, adminHeaders) | ||
|
|
||
| const res = await api.get("/admin/product-options", adminHeaders) | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.product_options.length).toEqual(1) | ||
| }) | ||
| }) | ||
| }, | ||
| }) | ||
This file contains hidden or 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
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.
q: do we validate
is_exclusivewhen assigning productsThere 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.
Not at the moment, you are right, I had planned to do most the error validation scenarios like that in the next PRs but forgot to add it in the list. Added it to the list