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

View File

@@ -1,5 +1,13 @@
// 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`] = `
Object {
"created_at": Any<String>,

View File

@@ -1,4 +1,5 @@
const path = require("path")
const { SalesChannel } = require("@medusajs/medusa")
const { useApi } = require("../../../helpers/use-api")
const { useDb } = require("../../../helpers/use-db")
@@ -15,7 +16,7 @@ const adminReqConfig = {
},
}
jest.setTimeout(30000)
jest.setTimeout(50000)
describe("sales channels", () => {
let medusaProcess
@@ -173,5 +174,109 @@ describe("sales channels", () => {
describe("GET /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)
})
})
})

View File

@@ -2,6 +2,7 @@ import {
AdminPostSalesChannelsReq,
AdminSalesChannelsRes,
AdminPostSalesChannelsSalesChannelReq,
AdminSalesChannelsDeleteRes,
} from "@medusajs/medusa"
import { ResponsePromise } from "../../typings"
import BaseResource from "../base"
@@ -48,17 +49,26 @@ class AdminSalesChannelsResource extends BaseResource {
return this.client.request("POST", path, payload, {}, customHeaders)
}
/* delete(
id: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<any> {
}*/
/* list(
query?: any,
customHeaders: Record<string, 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

View File

@@ -869,7 +869,7 @@ export const adminHandlers = [
discount_condition: {
...fixtures
.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,
})
)
}),
]

View File

@@ -2,6 +2,7 @@ import {
AdminPostSalesChannelsReq,
AdminSalesChannelsRes,
AdminPostSalesChannelsSalesChannelReq,
AdminSalesChannelsDeleteRes,
} from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js"
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
)
)
}

View File

@@ -1,6 +1,10 @@
import { renderHook } from "@testing-library/react-hooks"
import { useAdminCreateSalesChannel, useAdminUpdateSalesChannel } from "../../../../src"
import {
useAdminDeleteSalesChannel,
useAdminCreateSalesChannel,
useAdminUpdateSalesChannel,
} from "../../../../src"
import { fixtures } from "../../../../mocks/data"
import { createWrapper } from "../../../utils"
@@ -30,7 +34,7 @@ describe("useAdminCreateSalesChannel hook", () => {
})
describe("useAdminUpdateSalesChannel hook", () => {
test("updates a store", async () => {
test("updates a sales channel", async () => {
const salesChannel = {
name: "medusa sales channel",
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,
})
)
})
})

View File

@@ -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,
})
})
})
})

View File

@@ -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,
})
}

View File

@@ -1,5 +1,5 @@
import { Request, Response } from "express"
import SalesChannelService from "../../../../services/sales-channel"
import { SalesChannelService } from "../../../../services"
/**
* @oas [get] /sales-channels/{id}

View File

@@ -19,6 +19,10 @@ export default (app) => {
"/",
middlewares.wrap(require("./get-sales-channel").default)
)
salesChannelRouter.delete(
"/",
middlewares.wrap(require("./delete-sales-channel").default)
)
route.get("/", (req, res) => {})
@@ -34,7 +38,7 @@ export default (app) => {
middlewares.wrap(require("./update-sales-channel").default)
)
route.delete("/:id", (req, res) => {})
return app
}
@@ -43,7 +47,7 @@ export type AdminSalesChannelsRes = {
sales_channel: SalesChannel
}
export type AdminSalesChannelDeleteRes = DeleteResponse
export type AdminSalesChannelsDeleteRes = DeleteResponse
export type AdminSalesChannelListRes = PaginatedResponse & {
sales_channels: SalesChannel[]

View File

@@ -17,14 +17,16 @@ export const SalesChannelServiceMock = {
listAndCount: jest.fn().mockImplementation(() => {}),
delete: jest.fn().mockImplementation(() => {}),
create: jest.fn().mockImplementation((data) => {
return Promise.resolve({
id: id,
...data,
})
}),
delete: jest.fn().mockImplementation((id, config) => {
return Promise.resolve()
}),
}
const mock = jest.fn().mockImplementation(() => {

View File

@@ -28,6 +28,9 @@ describe("SalesChannelService", () => {
}
),
save: (salesChannel) => Promise.resolve(salesChannel),
softRemove: jest.fn().mockImplementation((id: string): any => {
return Promise.resolve()
}),
})
describe("retrieve", () => {
@@ -37,7 +40,7 @@ describe("SalesChannelService", () => {
salesChannelRepository: salesChannelRepositoryMock,
})
afterEach(() => {
beforeEach(() => {
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") }
)
})
})
})

View File

@@ -23,6 +23,7 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
static Events = {
UPDATED: "sales_channel.updated",
CREATED: "sales_channel.created",
DELETED: "sales_channel.deleted",
}
protected manager_: EntityManager
@@ -55,8 +56,8 @@ class SalesChannelService extends TransactionBaseService<SalesChannelService> {
salesChannelId: string,
config: FindConfig<SalesChannel> = {}
): Promise<SalesChannel | never> {
return await this.atomicPhase_(async (manager) => {
const salesChannelRepo = manager.getCustomRepository(
return await this.atomicPhase_(async (transactionManager) => {
const salesChannelRepo = transactionManager.getCustomRepository(
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,
})
})
}
}