Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./auth"
export * from "./availability"
export * from "./createBatchStore"
export * from "./line_items"
export * from "./orders"
export * from "./prices"
export * from "./sdk"
Expand Down
83 changes: 83 additions & 0 deletions packages/core/src/line_items/deleteLineItem.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { beforeEach, describe, expect, test, vi } from "vitest"
import type { InterceptorManager } from "#sdk"
import { deleteLineItem } from "./deleteLineItem.js"

const {
mockDelete,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
} = vi.hoisted(() => {
const mockDelete = vi.fn()
const mockAddRequestInterceptor = vi.fn().mockReturnValue(1)
const mockAddResponseInterceptor = vi.fn().mockReturnValue(1)
const mockAddRawResponseReader = vi.fn()
return {
mockDelete,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
}
})

vi.mock("@commercelayer/sdk/bundle", () => ({
CommerceLayer: vi.fn().mockReturnValue({
addRequestInterceptor: mockAddRequestInterceptor,
addResponseInterceptor: mockAddResponseInterceptor,
addRawResponseReader: mockAddRawResponseReader,
line_items: { delete: mockDelete },
}),
}))

vi.mock("@commercelayer/js-auth", () => ({
jwtDecode: vi.fn().mockReturnValue({ payload: { organization: { slug: "my-org" } } }),
}))

describe("deleteLineItem", () => {
beforeEach(() => {
vi.clearAllMocks()
mockAddRequestInterceptor.mockReturnValue(1)
mockAddResponseInterceptor.mockReturnValue(1)
mockDelete.mockResolvedValue(undefined)
})

test("resolves without a return value", async () => {
const result = await deleteLineItem({ accessToken: "fake-token", lineItemId: "li_1" })

expect(result).toBeUndefined()
})

test("calls line_items.delete with the correct id", async () => {
await deleteLineItem({ accessToken: "fake-token", lineItemId: "li_1" })

expect(mockDelete).toHaveBeenCalledWith("li_1")
})

test("forwards request interceptors to getSdk", async () => {
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { request: { onSuccess } }

await deleteLineItem({ accessToken: "fake-token", lineItemId: "li_1", interceptors })

expect(mockAddRequestInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
})

test("forwards response interceptors to getSdk", async () => {
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { response: { onSuccess } }

await deleteLineItem({ accessToken: "fake-token", lineItemId: "li_1", interceptors })

expect(mockAddResponseInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
})

test("does not register any interceptors when none are provided", async () => {
await deleteLineItem({ accessToken: "fake-token", lineItemId: "li_1" })

expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
expect(mockAddRawResponseReader).not.toHaveBeenCalled()
})
})
22 changes: 22 additions & 0 deletions packages/core/src/line_items/deleteLineItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getSdk } from "#sdk"
import type { RequestConfig } from "#types"

interface DeleteLineItemParams extends Pick<RequestConfig, "accessToken" | "interceptors"> {
lineItemId: string
}

/**
* Delete a line item by ID.
*
* @param {string} accessToken - The access token to use for authentication.
* @param {string} lineItemId - The ID of the line item to delete.
* @returns {Promise<void>}
*/
export async function deleteLineItem({
accessToken,
interceptors,
lineItemId,
}: DeleteLineItemParams): Promise<void> {
const sdk = getSdk({ accessToken, interceptors })
await sdk.line_items.delete(lineItemId)
}
113 changes: 113 additions & 0 deletions packages/core/src/line_items/getLineItems.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { beforeEach, describe, expect, test, vi } from "vitest"
import type { InterceptorManager } from "#sdk"
import { getLineItems } from "./getLineItems.js"

const {
mockRetrieve,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
} = vi.hoisted(() => {
const mockRetrieve = vi.fn()
const mockAddRequestInterceptor = vi.fn().mockReturnValue(1)
const mockAddResponseInterceptor = vi.fn().mockReturnValue(1)
const mockAddRawResponseReader = vi.fn()
return {
mockRetrieve,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
}
})

vi.mock("@commercelayer/sdk/bundle", () => ({
CommerceLayer: vi.fn().mockReturnValue({
addRequestInterceptor: mockAddRequestInterceptor,
addResponseInterceptor: mockAddResponseInterceptor,
addRawResponseReader: mockAddRawResponseReader,
orders: { retrieve: mockRetrieve },
}),
}))

vi.mock("@commercelayer/js-auth", () => ({
jwtDecode: vi.fn().mockReturnValue({ payload: { organization: { slug: "my-org" } } }),
}))

const MOCK_LINE_ITEMS = [
{ id: "li_1", item_type: "skus", quantity: 2 },
{ id: "li_2", item_type: "gift_cards", quantity: 1 },
]

describe("getLineItems", () => {
beforeEach(() => {
vi.clearAllMocks()
mockAddRequestInterceptor.mockReturnValue(1)
mockAddResponseInterceptor.mockReturnValue(1)
})

test("returns line_items from the retrieved order", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1", line_items: MOCK_LINE_ITEMS })

const result = await getLineItems({ accessToken: "fake-token", orderId: "order-1" })

expect(result).toEqual(MOCK_LINE_ITEMS)
})

test("returns an empty array when order has no line_items", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1" })

const result = await getLineItems({ accessToken: "fake-token", orderId: "order-1" })

expect(result).toEqual([])
})

test("calls orders.retrieve with the correct orderId and includes", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1", line_items: [] })

await getLineItems({ accessToken: "fake-token", orderId: "order-1" })

expect(mockRetrieve).toHaveBeenCalledWith(
"order-1",
expect.objectContaining({
include: expect.arrayContaining([
"line_items",
"line_items.line_item_options.sku_option",
"line_items.item",
]),
fields: { orders: ["line_items"] },
})
)
})

test("forwards request interceptors to getSdk", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1", line_items: [] })
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { request: { onSuccess } }

await getLineItems({ accessToken: "fake-token", orderId: "order-1", interceptors })

expect(mockAddRequestInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
})

test("forwards response interceptors to getSdk", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1", line_items: [] })
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { response: { onSuccess } }

await getLineItems({ accessToken: "fake-token", orderId: "order-1", interceptors })

expect(mockAddResponseInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
})

test("does not register any interceptors when none are provided", async () => {
mockRetrieve.mockResolvedValue({ id: "order-1", line_items: [] })

await getLineItems({ accessToken: "fake-token", orderId: "order-1" })

expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
expect(mockAddRawResponseReader).not.toHaveBeenCalled()
})
})
30 changes: 30 additions & 0 deletions packages/core/src/line_items/getLineItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { LineItem } from "@commercelayer/sdk"
import { getSdk } from "#sdk"
import type { RequestConfig } from "#types"

interface GetLineItemsParams extends Pick<RequestConfig, "accessToken" | "interceptors"> {
orderId: string
}

/**
* Retrieve all line items for a given order.
*
* Fetches the order with the necessary includes to fully populate
* line items, their options and the associated item resource.
*
* @param {string} accessToken - The access token to use for authentication.
* @param {string} orderId - The ID of the order whose line items to retrieve.
* @returns {Promise<LineItem[]>} - The list of line item resources.
*/
export async function getLineItems({
accessToken,
interceptors,
orderId,
}: GetLineItemsParams): Promise<LineItem[]> {
const sdk = getSdk({ accessToken, interceptors })
const order = await sdk.orders.retrieve(orderId, {
include: ["line_items", "line_items.line_item_options.sku_option", "line_items.item"],
fields: { orders: ["line_items"] },
})
return order.line_items ?? []
}
3 changes: 3 additions & 0 deletions packages/core/src/line_items/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { getLineItems } from "./getLineItems"
export { updateLineItem } from "./updateLineItem"
export { deleteLineItem } from "./deleteLineItem"
103 changes: 103 additions & 0 deletions packages/core/src/line_items/updateLineItem.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { beforeEach, describe, expect, test, vi } from "vitest"
import type { InterceptorManager } from "#sdk"
import { updateLineItem } from "./updateLineItem.js"

const {
mockUpdate,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
} = vi.hoisted(() => {
const mockUpdate = vi.fn()
const mockAddRequestInterceptor = vi.fn().mockReturnValue(1)
const mockAddResponseInterceptor = vi.fn().mockReturnValue(1)
const mockAddRawResponseReader = vi.fn()
return {
mockUpdate,
mockAddRequestInterceptor,
mockAddResponseInterceptor,
mockAddRawResponseReader,
}
})

vi.mock("@commercelayer/sdk/bundle", () => ({
CommerceLayer: vi.fn().mockReturnValue({
addRequestInterceptor: mockAddRequestInterceptor,
addResponseInterceptor: mockAddResponseInterceptor,
addRawResponseReader: mockAddRawResponseReader,
line_items: { update: mockUpdate },
}),
}))

vi.mock("@commercelayer/js-auth", () => ({
jwtDecode: vi.fn().mockReturnValue({ payload: { organization: { slug: "my-org" } } }),
}))

const MOCK_LINE_ITEM = { id: "li_1", item_type: "skus", quantity: 3 }

describe("updateLineItem", () => {
beforeEach(() => {
vi.clearAllMocks()
mockAddRequestInterceptor.mockReturnValue(1)
mockAddResponseInterceptor.mockReturnValue(1)
mockUpdate.mockResolvedValue(MOCK_LINE_ITEM)
})

test("returns the updated line item", async () => {
const result = await updateLineItem({
accessToken: "fake-token",
lineItemId: "li_1",
quantity: 3,
})

expect(result).toEqual(MOCK_LINE_ITEM)
})

test("calls line_items.update with correct id and quantity", async () => {
await updateLineItem({ accessToken: "fake-token", lineItemId: "li_1", quantity: 2 })

expect(mockUpdate).toHaveBeenCalledWith(
expect.objectContaining({ id: "li_1", quantity: 2 })
)
})

test("passes hasExternalPrice as _external_price", async () => {
await updateLineItem({
accessToken: "fake-token",
lineItemId: "li_1",
hasExternalPrice: true,
})

expect(mockUpdate).toHaveBeenCalledWith(
expect.objectContaining({ id: "li_1", _external_price: true })
)
})

test("forwards request interceptors to getSdk", async () => {
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { request: { onSuccess } }

await updateLineItem({ accessToken: "fake-token", lineItemId: "li_1", interceptors })

expect(mockAddRequestInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
})

test("forwards response interceptors to getSdk", async () => {
const onSuccess = vi.fn()
const interceptors: InterceptorManager = { response: { onSuccess } }

await updateLineItem({ accessToken: "fake-token", lineItemId: "li_1", interceptors })

expect(mockAddResponseInterceptor).toHaveBeenCalledWith(onSuccess, undefined)
expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
})

test("does not register any interceptors when none are provided", async () => {
await updateLineItem({ accessToken: "fake-token", lineItemId: "li_1" })

expect(mockAddRequestInterceptor).not.toHaveBeenCalled()
expect(mockAddResponseInterceptor).not.toHaveBeenCalled()
expect(mockAddRawResponseReader).not.toHaveBeenCalled()
})
})
Loading
Loading