feat(medusa, medusa-js, medusa-react): Implement Sales Channel deletion (#1804)

This commit is contained in:
Adrien de Peretti
2022-07-07 10:47:51 +02:00
committed by GitHub
parent 4d15e01c3e
commit 2d03634cfc
13 changed files with 368 additions and 21 deletions
@@ -1,5 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sales channels DELETE /admin/sales-channels/:id should delete the requested sales channel 1`] = `
Object {
"deleted": true,
"id": Any<String>,
"object": "sales-channel",
}
`;
exports[`sales channels GET /admin/sales-channels/:id should retrieve the requested sales channel 1`] = ` exports[`sales channels GET /admin/sales-channels/:id should retrieve the requested sales channel 1`] = `
Object { Object {
"created_at": Any<String>, "created_at": Any<String>,
@@ -1,4 +1,5 @@
const path = require("path") const path = require("path")
const { SalesChannel } = require("@medusajs/medusa")
const { useApi } = require("../../../helpers/use-api") const { useApi } = require("../../../helpers/use-api")
const { useDb } = require("../../../helpers/use-db") const { useDb } = require("../../../helpers/use-db")
@@ -15,7 +16,7 @@ const adminReqConfig = {
}, },
} }
jest.setTimeout(30000) jest.setTimeout(50000)
describe("sales channels", () => { describe("sales channels", () => {
let medusaProcess let medusaProcess
@@ -173,5 +174,109 @@ describe("sales channels", () => {
describe("GET /admin/sales-channels/:id", () => {}) describe("GET /admin/sales-channels/:id", () => {})
describe("POST /admin/sales-channels/:id", () => {}) describe("POST /admin/sales-channels/:id", () => {})
describe("DELETE /admin/sales-channels/:id", () => {})
describe("DELETE /admin/sales-channels/:id", () => {
let salesChannel
beforeEach(async() => {
try {
await adminSeeder(dbConnection)
salesChannel = await simpleSalesChannelFactory(dbConnection, {
name: "test name",
description: "test description",
})
} catch (e) {
console.error(e)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should delete the requested sales channel", async() => {
const api = useApi()
let deletedSalesChannel = await dbConnection.manager.findOne(SalesChannel, {
where: { id: salesChannel.id },
withDeleted: true
})
expect(deletedSalesChannel.id).toEqual(salesChannel.id)
expect(deletedSalesChannel.deleted_at).toEqual(null)
const response = await api.delete(
`/admin/sales-channels/${salesChannel.id}`,
adminReqConfig
)
expect(response.status).toEqual(200)
expect(response.data).toMatchSnapshot({
deleted: true,
id: expect.any(String),
object: "sales-channel",
})
deletedSalesChannel = await dbConnection.manager.findOne(SalesChannel, {
where: { id: salesChannel.id },
withDeleted: true
})
expect(deletedSalesChannel.id).toEqual(salesChannel.id)
expect(deletedSalesChannel.deleted_at).not.toEqual(null)
})
it("should delete the requested sales channel idempotently", async() => {
const api = useApi()
let deletedSalesChannel = await dbConnection.manager.findOne(SalesChannel, {
where: { id: salesChannel.id },
withDeleted: true
})
expect(deletedSalesChannel.id).toEqual(salesChannel.id)
expect(deletedSalesChannel.deleted_at).toEqual(null)
let response = await api.delete(
`/admin/sales-channels/${salesChannel.id}`,
adminReqConfig
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: expect.any(String),
object: "sales-channel",
deleted: true
})
deletedSalesChannel = await dbConnection.manager.findOne(SalesChannel, {
where: { id: salesChannel.id },
withDeleted: true
})
expect(deletedSalesChannel.id).toEqual(salesChannel.id)
expect(deletedSalesChannel.deleted_at).not.toEqual(null)
response = await api.delete(
`/admin/sales-channels/${salesChannel.id}`,
adminReqConfig
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: expect.any(String),
object: "sales-channel",
deleted: true
})
deletedSalesChannel = await dbConnection.manager.findOne(SalesChannel, {
where: { id: salesChannel.id },
withDeleted: true
})
expect(deletedSalesChannel.id).toEqual(salesChannel.id)
expect(deletedSalesChannel.deleted_at).not.toEqual(null)
})
})
}) })
@@ -2,6 +2,7 @@ import {
AdminPostSalesChannelsReq, AdminPostSalesChannelsReq,
AdminSalesChannelsRes, AdminSalesChannelsRes,
AdminPostSalesChannelsSalesChannelReq, AdminPostSalesChannelsSalesChannelReq,
AdminSalesChannelsDeleteRes,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { ResponsePromise } from "../../typings" import { ResponsePromise } from "../../typings"
import BaseResource from "../base" import BaseResource from "../base"
@@ -48,17 +49,26 @@ class AdminSalesChannelsResource extends BaseResource {
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
} }
/* delete(
id: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<any> {
}*/
/* list( /* list(
query?: any, query?: any,
customHeaders: Record<string, any> = {} customHeaders: Record<string, any> = {}
): ResponsePromise<any> { ): ResponsePromise<any> {
}*/ }*/
/**
* Delete a sales channel
* @experimental This feature is under development and may change in the future.
* To use this feature please enable featureflag `sales_channels` in your medusa backend project.
* @description gets a sales channel
* @returns an deletion result
*/
delete(
salesChannelId: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminSalesChannelsDeleteRes> {
const path = `/admin/sales-channels/${salesChannelId}`
return this.client.request("DELETE", path, {}, {}, customHeaders)
}
} }
export default AdminSalesChannelsResource export default AdminSalesChannelsResource
+12 -1
View File
@@ -869,7 +869,7 @@ export const adminHandlers = [
discount_condition: { discount_condition: {
...fixtures ...fixtures
.get("discount") .get("discount")
.rule.conditions.find((c) => c.id === req.params.conditionId), .rule.conditions.find(c => c.id === req.params.conditionId),
}, },
}) })
) )
@@ -1703,4 +1703,15 @@ export const adminHandlers = [
}) })
) )
}), }),
rest.delete("/admin/sales-channels/:id", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
id: req.params.id,
object: "sales-channel",
deleted: true,
})
)
}),
] ]
@@ -2,6 +2,7 @@ import {
AdminPostSalesChannelsReq, AdminPostSalesChannelsReq,
AdminSalesChannelsRes, AdminSalesChannelsRes,
AdminPostSalesChannelsSalesChannelReq, AdminPostSalesChannelsSalesChannelReq,
AdminSalesChannelsDeleteRes,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js" import { Response } from "@medusajs/medusa-js"
import { useMutation, UseMutationOptions, useQueryClient } from "react-query" import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
@@ -59,3 +60,30 @@ export const useAdminUpdateSalesChannel = (
) )
) )
} }
/**
* Delete a sales channel
* @experimental This feature is under development and may change in the future.
* To use this feature please enable featureflag `sales_channels` in your medusa backend project.
* @param id
* @param options
*/
export const useAdminDeleteSalesChannel = (
id: string,
options?: UseMutationOptions<
Response<AdminSalesChannelsDeleteRes>,
Error,
void
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
() => client.admin.salesChannels.delete(id),
buildOptions(
queryClient,
[adminSalesChannelsKeys.lists(), adminSalesChannelsKeys.detail(id)],
options
)
)
}
@@ -1,6 +1,10 @@
import { renderHook } from "@testing-library/react-hooks" import { renderHook } from "@testing-library/react-hooks"
import { useAdminCreateSalesChannel, useAdminUpdateSalesChannel } from "../../../../src" import {
useAdminDeleteSalesChannel,
useAdminCreateSalesChannel,
useAdminUpdateSalesChannel,
} from "../../../../src"
import { fixtures } from "../../../../mocks/data" import { fixtures } from "../../../../mocks/data"
import { createWrapper } from "../../../utils" import { createWrapper } from "../../../utils"
@@ -30,7 +34,7 @@ describe("useAdminCreateSalesChannel hook", () => {
}) })
describe("useAdminUpdateSalesChannel hook", () => { describe("useAdminUpdateSalesChannel hook", () => {
test("updates a store", async () => { test("updates a sales channel", async () => {
const salesChannel = { const salesChannel = {
name: "medusa sales channel", name: "medusa sales channel",
description: "main sales channel for medusa", description: "main sales channel for medusa",
@@ -57,3 +61,26 @@ describe("useAdminUpdateSalesChannel hook", () => {
}) })
}) })
}) })
describe("useAdminDeleteSalesChannel hook", () => {
test("deletes a sales channel", async () => {
const id = fixtures.get("sales_channel").id
const { result, waitFor } = renderHook(
() => useAdminDeleteSalesChannel(id),
{ wrapper: createWrapper() }
)
result.current.mutate()
await waitFor(() => result.current.isSuccess)
expect(result.current.data).toEqual(
expect.objectContaining({
id,
object: "sales-channel",
deleted: true,
})
)
})
})
@@ -0,0 +1,43 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { SalesChannelServiceMock } from "../../../../../services/__mocks__/sales-channel"
describe("DELETE /admin/sales-channels/:id", () => {
describe("successfully delete a sales channel", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/sales-channels/${IdMap.getId("sales_channel_1")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
flags: ["sales_channels"],
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls the delete method from the sales channel service", () => {
expect(SalesChannelServiceMock.delete).toHaveBeenCalledTimes(1)
expect(SalesChannelServiceMock.delete).toHaveBeenCalledWith(
IdMap.getId("sales_channel_1"),
)
})
it("returns the expected result", () => {
expect(subject.body).toEqual({
id: IdMap.getId("sales_channel_1"),
object: "sales-channel",
deleted: true,
})
})
})
})
@@ -0,0 +1,42 @@
import { Request, Response } from "express"
import { SalesChannelService } from "../../../../services/"
/**
* @oas [delete] /sales-channels/{id}
* operationId: "DeleteSalesChannelsSalesChannel"
* summary: "Delete a sales channel"
* description: "Deletes the sales channel."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Sales channel.
* tags:
* - Sales Channel
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted Sales Channel.
* object:
* type: string
* description: The type of the object that was deleted.
* deleted:
* type: boolean
*/
export default async (req: Request, res: Response): Promise<void> => {
const { id } = req.params
const salesChannelService: SalesChannelService = req.scope.resolve(
"salesChannelService"
)
await salesChannelService.delete(id)
res.json({
id,
object: "sales-channel",
deleted: true,
})
}
@@ -1,5 +1,5 @@
import { Request, Response } from "express" import { Request, Response } from "express"
import SalesChannelService from "../../../../services/sales-channel" import { SalesChannelService } from "../../../../services"
/** /**
* @oas [get] /sales-channels/{id} * @oas [get] /sales-channels/{id}
@@ -19,6 +19,10 @@ export default (app) => {
"/", "/",
middlewares.wrap(require("./get-sales-channel").default) middlewares.wrap(require("./get-sales-channel").default)
) )
salesChannelRouter.delete(
"/",
middlewares.wrap(require("./delete-sales-channel").default)
)
route.get("/", (req, res) => {}) route.get("/", (req, res) => {})
@@ -34,7 +38,7 @@ export default (app) => {
middlewares.wrap(require("./update-sales-channel").default) middlewares.wrap(require("./update-sales-channel").default)
) )
route.delete("/:id", (req, res) => {})
return app return app
} }
@@ -43,7 +47,7 @@ export type AdminSalesChannelsRes = {
sales_channel: SalesChannel sales_channel: SalesChannel
} }
export type AdminSalesChannelDeleteRes = DeleteResponse export type AdminSalesChannelsDeleteRes = DeleteResponse
export type AdminSalesChannelListRes = PaginatedResponse & { export type AdminSalesChannelListRes = PaginatedResponse & {
sales_channels: SalesChannel[] sales_channels: SalesChannel[]
@@ -17,14 +17,16 @@ export const SalesChannelServiceMock = {
listAndCount: jest.fn().mockImplementation(() => {}), listAndCount: jest.fn().mockImplementation(() => {}),
delete: jest.fn().mockImplementation(() => {}),
create: jest.fn().mockImplementation((data) => { create: jest.fn().mockImplementation((data) => {
return Promise.resolve({ return Promise.resolve({
id: id, id: id,
...data, ...data,
}) })
}), }),
delete: jest.fn().mockImplementation((id, config) => {
return Promise.resolve()
}),
} }
const mock = jest.fn().mockImplementation(() => { const mock = jest.fn().mockImplementation(() => {
@@ -28,6 +28,9 @@ describe("SalesChannelService", () => {
} }
), ),
save: (salesChannel) => Promise.resolve(salesChannel), save: (salesChannel) => Promise.resolve(salesChannel),
softRemove: jest.fn().mockImplementation((id: string): any => {
return Promise.resolve()
}),
}) })
describe("retrieve", () => { describe("retrieve", () => {
@@ -37,7 +40,7 @@ describe("SalesChannelService", () => {
salesChannelRepository: salesChannelRepositoryMock, salesChannelRepository: salesChannelRepositoryMock,
}) })
afterEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
@@ -92,4 +95,40 @@ describe("SalesChannelService", () => {
}) })
}) })
}) })
describe("delete", () => {
const salesChannelService = new SalesChannelService({
manager: MockManager,
eventBusService: EventBusServiceMock as unknown as EventBusService,
salesChannelRepository: salesChannelRepositoryMock
})
beforeEach(() => {
jest.clearAllMocks()
})
it('should soft remove a sales channel', async () => {
const res = await salesChannelService.delete(
IdMap.getId("sales_channel_1")
)
expect(res).toBeUndefined()
expect(salesChannelRepositoryMock.softRemove)
.toHaveBeenCalledTimes(1)
expect(salesChannelRepositoryMock.softRemove)
.toHaveBeenLastCalledWith({
id: IdMap.getId("sales_channel_1"),
...salesChannelData
})
expect(EventBusServiceMock.emit)
.toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit)
.toHaveBeenLastCalledWith(
SalesChannelService.Events.DELETED,
{ "id": IdMap.getId("sales_channel_1") }
)
})
})
}) })
+32 -4
View File
@@ -23,6 +23,7 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
static Events = { static Events = {
UPDATED: "sales_channel.updated", UPDATED: "sales_channel.updated",
CREATED: "sales_channel.created", CREATED: "sales_channel.created",
DELETED: "sales_channel.deleted",
} }
protected manager_: EntityManager protected manager_: EntityManager
@@ -55,8 +56,8 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
salesChannelId: string, salesChannelId: string,
config: FindConfig<SalesChannel> = {} config: FindConfig<SalesChannel> = {}
): Promise<SalesChannel | never> { ): Promise<SalesChannel | never> {
return await this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (transactionManager) => {
const salesChannelRepo = manager.getCustomRepository( const salesChannelRepo = transactionManager.getCustomRepository(
this.salesChannelRepository_ this.salesChannelRepository_
) )
@@ -139,8 +140,35 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
}) })
} }
async delete(id: string): Promise<void> { /**
throw new Error("Method not implemented.") * Deletes a sales channel from
* @experimental This feature is under development and may change in the future.
* To use this feature please enable the corresponding feature flag in your medusa backend project.
* @param salesChannelId - the id of the sales channel to delete
* @return Promise<void>
*/
async delete(salesChannelId: string): Promise<void> {
return await this.atomicPhase_(async (transactionManager) => {
const salesChannelRepo = transactionManager.getCustomRepository(
this.salesChannelRepository_
)
const salesChannel = await this.retrieve(salesChannelId).catch(
() => void 0
)
if (!salesChannel) {
return
}
await salesChannelRepo.softRemove(salesChannel)
await this.eventBusService_
.withTransaction(transactionManager)
.emit(SalesChannelService.Events.DELETED, {
id: salesChannelId,
})
})
} }
} }