Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 4106 nnnnat catalogItem GQL query #4200

Merged
merged 11 commits into from
Apr 23, 2018
34 changes: 34 additions & 0 deletions imports/plugins/core/catalog/server/queries/catalogItemProduct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Meteor } from "meteor/meteor";

/**
* @name catalogItemProduct
* @method
* @summary query the Catalog for a single Product by id or slug
* id takes priority if both are provided, throws meteor error if neither
* @param {Object} context - an object containing the per-request state
* @param {Object} params - request parameters
* @param {String} [params._id] - Product id to include
* @param {String} [param.slug] - Product slug (handle)
* @return {Object} - A Product from the Catalog
*/
export default async function catalogItemProduct(context, { _id, slug } = {}) {
const { collections } = context;
const { Catalog } = collections;

if (!_id && !slug) {
throw new Meteor.Error("invalid-param", "You must provide a product slug or product id");
}

const query = {
isDeleted: { $ne: true },
isVisible: { $ne: false }
};

if (_id) {
query._id = _id;
} else {
query.handle = slug;
}

return Catalog.findOne(query);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import mockContext from "/imports/test-utils/helpers/mockContext";
import catalogItemProduct from "./catalogItemProduct";

const productId = "cmVhY3Rpb24vY2F0YWxvZ0l0ZW06MTIz"; // reaction/catalogItem:123
const productSlug = "PRODUCT_SLUG";
const mockQueryBase = {
isDeleted: { $ne: true },
isVisible: { $ne: false }
};

beforeEach(() => {
jest.resetAllMocks();
});

// expect query by product slug
test("returns a product from the catalog collection by product slug", async () => {
const query = { ...mockQueryBase, handle: productSlug };
mockContext.collections.Catalog.findOne.mockReturnValueOnce("CATALOGPRODUCT");
const result = await catalogItemProduct(mockContext, { slug: productSlug });
expect(mockContext.collections.Catalog.findOne).toHaveBeenCalledWith(query);
expect(result).toBe("CATALOGPRODUCT");
});

// expect query by product _id
test("returns a product from the catalog collection by product id", async () => {
const query = { ...mockQueryBase, _id: productId };
mockContext.collections.Catalog.findOne.mockReturnValueOnce("CATALOGPRODUCT");
const result = await catalogItemProduct(mockContext, { _id: productId });
expect(mockContext.collections.Catalog.findOne).toHaveBeenCalledWith(query);
expect(result).toBe("CATALOGPRODUCT");
});

// expect query by id if both slug and id are provided as params
test("returns a product from the catalog collection by product id if both slug and id are provided as params", async () => {
const query = { ...mockQueryBase, _id: productId };
mockContext.collections.Catalog.findOne.mockReturnValueOnce("CATALOGPRODUCT");
const result = await catalogItemProduct(mockContext, { _id: productId, slug: productSlug });
expect(mockContext.collections.Catalog.findOne).toHaveBeenCalledWith(query);
expect(result).toBe("CATALOGPRODUCT");
});

// expect to throw Error if neither slug or id is provided
test("throws an error if neither product id or slug is provided", async () => {
const results = catalogItemProduct(mockContext, {});
return expect(results).rejects.toThrow();
});
2 changes: 2 additions & 0 deletions imports/plugins/core/graphql/server/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { userAccountQuery } from "/imports/plugins/core/accounts/server/methods/
import { groupQuery, groupsQuery } from "/imports/plugins/core/accounts/server/methods/groupQuery";
import { rolesQuery } from "/imports/plugins/core/accounts/server/methods/rolesQuery";
import catalogItems from "/imports/plugins/core/catalog/server/queries/catalogItems";
import catalogItemProduct from "/imports/plugins/core/catalog/server/queries/catalogItemProduct";
import tags from "/imports/plugins/core/catalog/server/queries/tags";
import tagsByIds from "/imports/plugins/core/catalog/server/queries/tagsByIds";
import getShopIdByDomain from "/imports/plugins/core/accounts/server/no-meteor/getShopIdByDomain";

export default {
catalogItems,
catalogItemProduct,
group: groupQuery,
groups: groupsQuery,
primaryShopId: getShopIdByDomain,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { decodeCatalogItemOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/catalogItem";

/**
* @name catalogItemProduct
* @method
* @summary Get a CatalogItemProduct from the Catalog
* @param {Object} _ - unused
* @param {ConnectionArgs} args - an object of all arguments that were sent by the client
* @param {String} args.slugOrId - slug or id for catalog item product
* @param {Object} context - an object containing the per-request state
* @return {Promise<Object>} A CatalogItemProduct object
*/
export default async function catalogItemProduct(_, args, context) {
const { slugOrId } = args;

let productId;
let productSlug;
try {
productId = decodeCatalogItemOpaqueId(slugOrId);
} catch (error) {
productSlug = slugOrId;
}

return context.queries.catalogItemProduct(context, {
_id: productId,
slug: productSlug
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import catalogItemProductResolver from "./catalogItemProduct";

const mockItem = {
_id: "a1",
name: "Item Product"
};

const productSlug = "PRODUCT_SLUG";
const productId = "cmVhY3Rpb24vY2F0YWxvZ0l0ZW06MTIz"; // reaction/catalogItem:123

// product slug
test("calls queries.catalogItemProduct with a product slug and return a CatalogItemProduct", async () => {
const slugOrId = productSlug;
const catalogItemProduct = jest
.fn()
.mockName("queries.catalogItemProduct")
.mockReturnValueOnce(Promise.resolve(mockItem));
const result = await catalogItemProductResolver(
{},
{
slugOrId
},
{
queries: { catalogItemProduct }
}
);

expect(catalogItemProduct).toHaveBeenCalledWith(jasmine.any(Object), { _id: undefined, slug: productSlug });
expect(result).toEqual(mockItem);
});

// product id
test("calls queries.catalogItemProduct with a product id and return a CatalogItemProduct", async () => {
const slugOrId = productId;
const catalogItemProduct = jest
.fn()
.mockName("queries.catalogItemProduct")
.mockReturnValueOnce(Promise.resolve(mockItem));
const result = await catalogItemProductResolver(
{},
{
slugOrId
},
{
queries: { catalogItemProduct }
}
);

expect(catalogItemProduct).toHaveBeenCalledWith(jasmine.any(Object), { _id: "123", slug: undefined });
expect(result).toEqual(mockItem);
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import catalogItems from "./catalogItems";
import catalogItemProduct from "./catalogItemProduct";

export default {
catalogItems
catalogItems,
catalogItemProduct
};
5 changes: 5 additions & 0 deletions imports/plugins/core/graphql/server/schemas/catalog.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,9 @@ extend type Query {
"By default, items are sorted by when they were created, newest first. Set this to sort by one of the other allowed fields"
sortBy: CatalogItemSortByField = createdAt
): CatalogItemConnection
"Gets product from catalog"
catalogItemProduct(
"Provide either a product ID or slug"
slugOrId: String
): CatalogItemProduct
}
145 changes: 145 additions & 0 deletions tests/catalog/CatalogItemProductFullQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
($slugOrId: String!) {
catalogItemProduct(slugOrId: $slugOrId) {
_id
shop {
_id
}
createdAt
updatedAt
positions {
displayWeight
isPinned
position
tagId
updatedAt
}
product {
_id
barcode
compareAtPrice
createdAt
description
height
isBackorder
isLowQuantity
isSoldOut
isTaxable
length
lowInventoryWarningThreshold
metafields {
value
namespace
description
valueType
scope
key
}
metaDescription
minOrderQuantity
originCountry
pageTitle
parcel {
containers
length
width
height
weight
}
price {
max
min
range
}
media {
toGrid
priority
productId
variantId
URLs {
thumbnail
small
medium
large
original
}
}
primaryImage {
toGrid
priority
productId
variantId
URLs {
thumbnail
small
medium
large
original
}
}
productId
productType
requiresShipping
shop {
_id
}
sku
slug
socialMetadata {
service
message
}
tagIds
tags(first: 2) {
nodes {
_id
}
}
taxCode
taxDescription
title
updatedAt
variants {
_id
ancestorIds
barcode
compareAtPrice
createdAt
height
index
inventoryManagement
inventoryPolicy
isLowQuantity
isSoldOut
isTaxable
length
lowInventoryWarningThreshold
metafields {
value
namespace
description
valueType
scope
key
}
minOrderQuantity
optionTitle
originCountry
price
shop {
_id
}
sku
taxCode
taxDescription
title
updatedAt
variantId
weight
width
}
vendor
weight
width
}
}
}
Loading