feat: add product status (#400)
* added statuses to product + unit test for updating status * add update to product model * added integration tests * added integration test to validate that updating status to null results in invalid_data error * removed comment * update GET /store/products integration test * fixed unit test with IdMap * changed dbehaviour on invalid status input on admin list products * updated migration to add status = published on all existing products + added integration test on GET /admin/products when status null is provided * made requested changes to migration and GET /store/products * fixed test * made requested changes to migration
This commit is contained in:
1 parent
49a132976d
commit
a82332da3e
17 files changed
+255
-36
No files matched your search
@@ -110,6 +110,7 @@ describe("POST /admin/products", () => {
|
||||
description: "Test Description",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
status: "draft",
|
||||
is_giftcard: false,
|
||||
options: [{ title: "Denominations" }],
|
||||
profile_id: IdMap.getId("default_shipping_profile"),
|
||||
@@ -170,6 +171,7 @@ describe("POST /admin/products", () => {
|
||||
options: [{ title: "Denominations" }],
|
||||
handle: "test-gift-card",
|
||||
is_giftcard: true,
|
||||
status: "draft",
|
||||
profile_id: IdMap.getId("giftCardProfile"),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -193,6 +193,9 @@ export default async (req, res) => {
|
||||
.optional(),
|
||||
thumbnail: Validator.string().optional(),
|
||||
handle: Validator.string().optional(),
|
||||
status: Validator.string()
|
||||
.valid("proposed", "draft", "published", "rejected")
|
||||
.default("draft"),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,20 @@ export default async (req, res) => {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
if ("status" in req.query) {
|
||||
const schema = Validator.array()
|
||||
.items(
|
||||
Validator.string().valid("proposed", "draft", "published", "rejected")
|
||||
)
|
||||
.single()
|
||||
|
||||
const { value, error } = schema.validate(req.query.status)
|
||||
|
||||
if (value && !error) {
|
||||
selector.status = value
|
||||
}
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: includeFields.length ? includeFields : defaultFields,
|
||||
relations: expandFields.length ? expandFields : defaultRelations,
|
||||
|
||||
@@ -193,6 +193,12 @@ export default async (req, res) => {
|
||||
.allow(null, ""),
|
||||
description: Validator.string().optional(),
|
||||
discountable: Validator.boolean().optional(),
|
||||
status: Validator.string().valid(
|
||||
"proposed",
|
||||
"draft",
|
||||
"published",
|
||||
"rejected"
|
||||
),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("GET /store/products", () => {
|
||||
it("calls get product from productSerice", () => {
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledWith(
|
||||
{},
|
||||
{ status: ["published"] },
|
||||
{ relations: defaultRelations, skip: 0, take: 100 }
|
||||
)
|
||||
})
|
||||
@@ -43,7 +43,7 @@ describe("GET /store/products", () => {
|
||||
it("calls list from productSerice", () => {
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledWith(
|
||||
{ is_giftcard: true },
|
||||
{ is_giftcard: true, status: ["published"] },
|
||||
{ relations: defaultRelations, skip: 0, take: 100 }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,8 @@ export default async (req, res) => {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
selector.status = ["published"]
|
||||
|
||||
const listConfig = {
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
|
||||
@@ -28,10 +28,7 @@ describe("Get variant by id", () => {
|
||||
describe("get variant with prices", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"GET",
|
||||
`/store/variants/${IdMap.getId("variantWithPrices")}`
|
||||
)
|
||||
subject = await request("GET", `/store/variants/variant_with_prices`)
|
||||
})
|
||||
it("successfully retrieves variants with prices", async () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class statusOnProduct1631864388026 implements MigrationInterface {
|
||||
name = "statusOnProduct1631864388026"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "product_status_enum" AS ENUM('draft', 'proposed', 'published', 'rejected')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ADD "status" "product_status_enum" `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`UPDATE "product" SET "status" = 'published' WHERE "status" IS NULL`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ALTER COLUMN "status" SET NOT NULL`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ALTER COLUMN "status" SET DEFAULT 'draft'`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "product" DROP COLUMN "status"`)
|
||||
await queryRunner.query(`DROP TYPE "product_status_enum"`)
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,13 @@ import { ProductVariant } from "./product-variant"
|
||||
import { ShippingProfile } from "./shipping-profile"
|
||||
import _ from "lodash"
|
||||
|
||||
export enum Status {
|
||||
DRAFT = "draft",
|
||||
PROPOSED = "proposed",
|
||||
PUBLISHED = "published",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Product {
|
||||
@PrimaryColumn()
|
||||
@@ -47,6 +54,9 @@ export class Product {
|
||||
@Column({ default: false })
|
||||
is_giftcard: boolean
|
||||
|
||||
@DbAwareColumn({ type: "enum", enum: Status, default: "draft" })
|
||||
status: Status
|
||||
|
||||
@ManyToMany(() => Image, { cascade: ["insert"] })
|
||||
@JoinTable({
|
||||
name: "product_images",
|
||||
|
||||
@@ -223,7 +223,7 @@ export const ProductVariantServiceMock = {
|
||||
if (variantId === "4") {
|
||||
return Promise.resolve(variant4)
|
||||
}
|
||||
if (variantId === IdMap.getId("variantWithPrices")) {
|
||||
if (variantId === "variant_with_prices") {
|
||||
return Promise.resolve(variantWithPrices)
|
||||
}
|
||||
if (variantId === IdMap.getId("validId")) {
|
||||
|
||||
@@ -199,6 +199,12 @@ describe("ProductService", () => {
|
||||
variants: [{ id: IdMap.getId("green"), title: "Green" }],
|
||||
})
|
||||
}
|
||||
if (query.where.id === "prod_status") {
|
||||
return Promise.resolve({
|
||||
id: "prod_status",
|
||||
status: "draft",
|
||||
})
|
||||
}
|
||||
if (query.where.id === "123") {
|
||||
return undefined
|
||||
}
|
||||
@@ -290,6 +296,18 @@ describe("ProductService", () => {
|
||||
expect(productRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully updates product status", async () => {
|
||||
await productService.update(IdMap.getId("ironman"), {
|
||||
status: "published",
|
||||
})
|
||||
|
||||
expect(productRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(productRepository.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("ironman"),
|
||||
status: "published",
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully updates product", async () => {
|
||||
await productService.update(IdMap.getId("ironman"), {
|
||||
title: "Full suit",
|
||||
|
||||
Reference in new issue
Block a user