feat: add and remove products to/from collection in bulk endpoints (#1032)

* adds bulk add/remove products to/from collection. Adds endpoint updateProducts on collections that uses these bulk operations

* fix integration tests and test description

* undo change to swap

* made requested changes

* added removeProducts endpoint

* made requested changes

* fix: set collection_id null

* updated collection_id to type string | undefined
This commit is contained in:
Kasper Fabricius Kristensen
2022-02-16 22:45:19 +01:00
committed by GitHub
parent 22d3d4a2f6
commit 66294038f0
14 changed files with 580 additions and 73 deletions
@@ -0,0 +1,91 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("POST /admin/collections/:id/products/batch", () => {
describe("successfully adds products to collection", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/collections/${IdMap.getId("col")}/products/batch`,
{
payload: {
product_ids: ["prod_1", "prod_2"],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns updated collection with new products", () => {
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
})
it("product collection service update", () => {
expect(ProductCollectionServiceMock.addProducts).toHaveBeenCalledTimes(1)
expect(
ProductCollectionServiceMock.addProducts
).toHaveBeenCalledWith(IdMap.getId("col"), ["prod_1", "prod_2"])
})
})
describe("error on non-existing collection", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/collections/null/products/batch`,
{
payload: {
product_ids: ["prod_1", "prod_2"],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("throws error", () => {
expect(subject.body.message).toBe("Product collection not found")
})
})
describe("error invalid request", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/collections/${IdMap.getId("col")}/products/batch`,
{
payload: {
product_ids: [],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
})
})
@@ -0,0 +1,64 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("DELETE /admin/collections/:id/products/batch", () => {
describe("successfully removes products from collection", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/collections/${IdMap.getId("col")}/products/batch`,
{
payload: {
product_ids: ["prod_1", "prod_2"],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("product collection service remove products", () => {
expect(ProductCollectionServiceMock.removeProducts).toHaveBeenCalledTimes(
1
)
expect(
ProductCollectionServiceMock.removeProducts
).toHaveBeenCalledWith(IdMap.getId("col"), ["prod_1", "prod_2"])
})
})
describe("error on invalid request", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/collections/${IdMap.getId("col")}/products/batch`,
{
payload: {
product_ids: [],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
})
})
@@ -0,0 +1,52 @@
import { ArrayNotEmpty, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /collections/{id}/products/batch
* operationId: "PostProductsToCollection"
* summary: "Updates products associated with a Product Collection"
* description: "Updates products associated with a Product Collection"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* product_ids:
* description: "An array of Product IDs to add to the Product Collection."
* type: array
* items:
* properties:
* id:
* description: "The ID of a Product to add to the Product Collection."
* type: string
* tags:
* - Collection
* responses:
* "200":
* description: OK
*/
export default async (req, res) => {
const { id } = req.params
const validated = await validator(AdminPostProductsToCollectionReq, req.body)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const collection = await productCollectionService.addProducts(
id,
validated.product_ids
)
res.status(200).json({ collection })
}
export class AdminPostProductsToCollectionReq {
@ArrayNotEmpty()
@IsString({ each: true })
product_ids: string[]
}
@@ -17,6 +17,9 @@ export default (app) => {
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
route.post("/:id/products/batch", middlewares.wrap(require("./add-products").default))
route.delete("/:id/products/batch", middlewares.wrap(require("./remove-products").default))
return app
}
@@ -0,0 +1,56 @@
import { ArrayNotEmpty, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [delete] /collections/{id}/products/batch
* operationId: "DeleteProductsFromCollection"
* summary: "Removes products associated with a Product Collection"
* description: "Removes products associated with a Product Collection"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* requestBody:
* content:
* application/json:
* schema:
* properties:
* product_ids:
* description: "An array of Product IDs to remove from the Product Collection."
* type: array
* items:
* properties:
* id:
* description: "The ID of a Product to remove from the Product Collection."
* type: string
* tags:
* - Collection
* responses:
* "200":
* description: OK
*/
export default async (req, res) => {
const { id } = req.params
const validated = await validator(
AdminDeleteProductsFromCollectionReq,
req.body
)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.removeProducts(id, validated.product_ids)
res.json({
id,
object: "product-collection",
removed_products: validated.product_ids,
})
}
export class AdminDeleteProductsFromCollectionReq {
@ArrayNotEmpty()
@IsString({ each: true })
product_ids: string[]
}