We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kuro-tomo/shopify-agentic-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
/**
* REST Handler — Cart (isolated unit tests with mocks)
* Targets 95%+ coverage of src/rest/cart.ts
*/
vi.mock('../../src/tools/manage-cart.js', () => ({
manageCart: vi.fn(),
}));
import { handleCart } from '../../src/rest/cart.js';
import type { RESTRequest } from '../../src/rest/types.js';
import { manageCart } from '../../src/tools/manage-cart.js';
// ─── Helpers ───
function makeReq(overrides: Partial<RESTRequest> = {}): RESTRequest {
return {
method: 'GET',
path: '/ucp/v1/cart',
segments: [],
query: {},
body: null,
headers: {},
...overrides,
};
}
// ─── Setup ───
beforeEach(() => {
vi.clearAllMocks();
});
// ─── Tests ───
describe('handleCart', () => {
// 1. POST create cart — returns 201
it('POST with empty segments creates a new cart and returns 201', async () => {
const mockResult = { cart_id: 'cart-1', line_items: [], item_count: 0 };
vi.mocked(manageCart).mockResolvedValue(mockResult as any);
const res = await handleCart(makeReq({ method: 'POST' }));
expect(res.statusCode).toBe(201);
expect(res.body).toEqual(mockResult);
expect(manageCart).toHaveBeenCalledWith({ action: 'create' });
});
// 2. GET cart by ID — returns 200
it('GET /{cartId} returns 200 with cart data', async () => {
const mockResult = { cart_id: 'cart-1', line_items: [], item_count: 0 };
vi.mocked(manageCart).mockResolvedValue(mockResult as any);
const res = await handleCart(makeReq({ segments: ['cart-1'] }));
expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockResult);
expect(manageCart).toHaveBeenCalledWith({ action: 'get', cart_id: 'cart-1' });
});
// 3. POST add item with valid body — returns 200
it('POST /{cartId}/items with valid body adds item and returns 200', async () => {
const mockResult = {
cart_id: 'cart-1',
line_items: [{ variant_id: 'v1', quantity: 3 }],
item_count: 3,
};
vi.mocked(manageCart).mockResolvedValue(mockResult as any);
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { variant_id: 'v1', quantity: 3 },
}));
expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockResult);
expect(manageCart).toHaveBeenCalledWith({
action: 'add',
cart_id: 'cart-1',
variant_id: 'v1',
quantity: 3,
});
});
// 4. POST add item with invalid body — returns 400
it('POST /{cartId}/items without variant_id returns 400', async () => {
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { quantity: 2 },
}));
expect(res.statusCode).toBe(400);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('bad_request');
expect(manageCart).not.toHaveBeenCalled();
});
// 5. DELETE remove item — returns 200
it('DELETE /{cartId}/items/{variantId} removes item and returns 200', async () => {
const mockResult = { cart_id: 'cart-1', line_items: [], item_count: 0 };
vi.mocked(manageCart).mockResolvedValue(mockResult as any);
const res = await handleCart(makeReq({
method: 'DELETE',
segments: ['cart-1', 'items', 'v1'],
}));
expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockResult);
expect(manageCart).toHaveBeenCalledWith({
action: 'remove',
cart_id: 'cart-1',
variant_id: 'v1',
});
});
// 6. PUT on / — returns 405
it('PUT on root returns 405 method not allowed (POST only)', async () => {
const res = await handleCart(makeReq({ method: 'PUT' }));
expect(res.statusCode).toBe(405);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('method_not_allowed');
expect(body.error.message).toContain('POST');
});
// 7. PUT on /{id} — returns 405
it('PUT on /{cartId} returns 405 method not allowed (GET only)', async () => {
const res = await handleCart(makeReq({ method: 'PUT', segments: ['cart-1'] }));
expect(res.statusCode).toBe(405);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('method_not_allowed');
expect(body.error.message).toContain('GET');
});
// 8. Invalid route — returns 400
it('GET with unexpected segment structure returns 400 invalid route', async () => {
const res = await handleCart(makeReq({
segments: ['cart-1', 'unknown', 'extra', 'segments'],
}));
expect(res.statusCode).toBe(400);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('bad_request');
expect(body.error.message).toContain('Invalid cart route');
});
// 9. Error with 'not found' → 404
it('catches errors containing "not found" and returns 404', async () => {
vi.mocked(manageCart).mockRejectedValue(new Error('Cart not found'));
const res = await handleCart(makeReq({ segments: ['nonexistent'] }));
expect(res.statusCode).toBe(404);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('not_found');
expect(body.error.message).toContain('not found');
});
// 10. Error with 'required' → 400
it('catches errors containing "required" and returns 400', async () => {
vi.mocked(manageCart).mockRejectedValue(new Error('cart_id is required'));
const res = await handleCart(makeReq({ segments: ['cart-1'] }));
expect(res.statusCode).toBe(400);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('bad_request');
expect(body.error.message).toContain('required');
});
// 11. Generic error → 500
it('catches generic errors and returns 500', async () => {
vi.mocked(manageCart).mockRejectedValue(new Error('Internal storage failure'));
const res = await handleCart(makeReq({ segments: ['cart-1'] }));
expect(res.statusCode).toBe(500);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('internal_error');
expect(body.error.message).toContain('Internal storage failure');
});
// Additional edge cases
it('POST /{cartId}/items with missing quantity returns 400', async () => {
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { variant_id: 'v1' },
}));
expect(res.statusCode).toBe(400);
});
it('POST /{cartId}/items with negative quantity returns 400', async () => {
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { variant_id: 'v1', quantity: -1 },
}));
expect(res.statusCode).toBe(400);
});
it('POST /{cartId}/items with non-integer quantity returns 400', async () => {
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { variant_id: 'v1', quantity: 1.5 },
}));
expect(res.statusCode).toBe(400);
});
it('POST /{cartId}/items with empty variant_id returns 400', async () => {
const res = await handleCart(makeReq({
method: 'POST',
segments: ['cart-1', 'items'],
body: { variant_id: '', quantity: 1 },
}));
expect(res.statusCode).toBe(400);
});
it('PUT on /{cartId}/items returns 405 (POST only)', async () => {
const res = await handleCart(makeReq({
method: 'PUT',
segments: ['cart-1', 'items'],
}));
expect(res.statusCode).toBe(405);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('method_not_allowed');
expect(body.error.message).toContain('POST');
});
it('PUT on /{cartId}/items/{variantId} returns 405 (DELETE only)', async () => {
const res = await handleCart(makeReq({
method: 'PUT',
segments: ['cart-1', 'items', 'v1'],
}));
expect(res.statusCode).toBe(405);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('method_not_allowed');
expect(body.error.message).toContain('DELETE');
});
it('catches non-Error thrown values and converts to string', async () => {
vi.mocked(manageCart).mockRejectedValue('unexpected throw');
const res = await handleCart(makeReq({ method: 'POST' }));
expect(res.statusCode).toBe(500);
const body = res.body as { error: { code: string; message: string } };
expect(body.error.code).toBe('internal_error');
expect(body.error.message).toBe('unexpected throw');
});
it('POST create where manageCart throws "Not Found" returns 404', async () => {
vi.mocked(manageCart).mockRejectedValue(new Error('Resource Not Found'));
const res = await handleCart(makeReq({ method: 'POST' }));
expect(res.statusCode).toBe(404);
});
it('DELETE where manageCart throws "required" returns 400', async () => {
vi.mocked(manageCart).mockRejectedValue(new Error('variant_id is required'));
const res = await handleCart(makeReq({
method: 'DELETE',
segments: ['cart-1', 'items', 'v1'],
}));
expect(res.statusCode).toBe(400);
});
});