Skip to content

Commit

Permalink
Refactor product orchestration and update mocks for product add and …
Browse files Browse the repository at this point in the history
…destroy operations

 - Implement CustomerProductAddOrchestration and CustomerProductDestroyOrchestration
 - Update mocks to reflect changes in product add and destroy orchestrations
 - Remove unnecessary request parameter from CustomerProductControllerUpdate
 - Adjust updateArticle to accept customerId instead of user object
 - Add new tests for destroy-product orchestration
 - Remove redundant shopifyAdmin requests from product destroy service and tests
  • Loading branch information
jamalsoueidan committed May 29, 2024
1 parent 9b7e851 commit f2491dd
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 68 deletions.
1 change: 1 addition & 0 deletions src/functions/customer-product.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ app.http("customerProductDestroy", {
authLevel: "anonymous",
route: "customer/{customerId?}/product/{productId?}",
handler: CustomerProductControllerDestroy,
extraInputs: [df.input.durableClient()],
});
4 changes: 2 additions & 2 deletions src/functions/customer/controllers/product/add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import {

require("~/library/jest/mongoose/mongodb.jest");

jest.mock("../../orchestrations/product/update", () => ({
CustomerProductUpdateOrchestration: () => ({
jest.mock("../../orchestrations/product/add", () => ({
CustomerProductAddOrchestration: () => ({
request: jest.fn(),
}),
}));
Expand Down
4 changes: 2 additions & 2 deletions src/functions/customer/controllers/product/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { HttpRequest, InvocationContext } from "@azure/functions";
import { ScheduleProductZodSchema } from "~/functions/schedule";
import { _ } from "~/library/handler";
import { GidFormat, StringOrObjectId } from "~/library/zod";
import { CustomerProductUpdateOrchestration } from "../../orchestrations/product/update";
import { CustomerProductAddOrchestration } from "../../orchestrations/product/add";
import { CustomerProductServiceAdd } from "../../services/product/add";

export type CustomerProductControllerAddRequest = {
Expand Down Expand Up @@ -53,7 +53,7 @@ export const CustomerProductControllerAdd = _(
validateBody
);

await CustomerProductUpdateOrchestration(
await CustomerProductAddOrchestration(
{ productId: product.productId, customerId: validateQuery.customerId },
context
);
Expand Down
18 changes: 3 additions & 15 deletions src/functions/customer/controllers/product/destroy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {

import { getProductObject } from "~/library/jest/helpers/product";
import { createScheduleWithProducts } from "~/library/jest/helpers/schedule";
import { shopifyAdmin } from "~/library/shopify";
import {
CustomerProductControllerDestroy,
CustomerProductControllerDestroyRequest,
Expand All @@ -18,14 +17,12 @@ import {

require("~/library/jest/mongoose/mongodb.jest");

jest.mock("~/library/shopify", () => ({
shopifyAdmin: jest.fn().mockReturnValue({
jest.mock("../../orchestrations/product/destroy", () => ({
CustomerProductDestroyOrchestration: () => ({
request: jest.fn(),
}),
}));

const mockRequest = shopifyAdmin().request as jest.Mock;

describe("CustomerProductControllerDestroy", () => {
let context: InvocationContext;
let request: HttpRequest;
Expand Down Expand Up @@ -53,21 +50,12 @@ describe("CustomerProductControllerDestroy", () => {
products: [product],
});

mockRequest.mockResolvedValueOnce({
data: {
productDelete: {
deletedProductId: {
id: "123",
},
},
},
});

request = await createHttpRequest<CustomerProductControllerDestroyRequest>({
query: {
customerId: newSchedule.customerId,
productId: product.productId,
},
context,
});

const res: HttpSuccessResponse<CustomerProductControllerDestroyResponse> =
Expand Down
12 changes: 11 additions & 1 deletion src/functions/customer/controllers/product/destroy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { InvocationContext } from "@azure/functions";
import { z } from "zod";
import {
ScheduleProductZodSchema,
ScheduleZodSchema,
} from "~/functions/schedule/schedule.types";
import { _ } from "~/library/handler";
import { CustomerProductDestroyOrchestration } from "../../orchestrations/product/destroy";
import { CustomerProductServiceDestroy } from "../../services/product/destroy";
import { CustomerProductServiceGet } from "../../services/product/get";

export type CustomerProductControllerDestroyRequest = {
query: z.infer<typeof CustomerProductControllerDestroyQuerySchema>;
context: InvocationContext;
};

const CustomerProductControllerDestroyQuerySchema = z.object({
Expand All @@ -20,9 +24,15 @@ export type CustomerProductControllerDestroyResponse = Awaited<
>;

export const CustomerProductControllerDestroy = _(
({ query }: CustomerProductControllerDestroyRequest) => {
async ({ query, context }: CustomerProductControllerDestroyRequest) => {
const validateQuery =
CustomerProductControllerDestroyQuerySchema.parse(query);

// we must find the product before throwing it to durable function, since destroy may delete the product before
// we can get hold of the product.options in the durable activate functions.
const product = await CustomerProductServiceGet(validateQuery);
await CustomerProductDestroyOrchestration({ product }, context);

return CustomerProductServiceDestroy(validateQuery);
}
);
1 change: 0 additions & 1 deletion src/functions/customer/controllers/product/update.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ describe("CustomerProductControllerUpdate", () => {
description: "hej med dig",
},
context,
request,
});

const res: HttpSuccessResponse<CustomerProductControllerUpdateResponse> =
Expand Down
11 changes: 3 additions & 8 deletions src/functions/customer/controllers/product/update.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from "zod";
import { ScheduleProductZodSchema } from "~/functions/schedule/schedule.types";

import { HttpRequest, InvocationContext } from "@azure/functions";
import { InvocationContext } from "@azure/functions";
import { _ } from "~/library/handler";
import { GidFormat } from "~/library/zod";
import { CustomerProductUpdateOrchestration } from "../../orchestrations/product/update";
Expand All @@ -11,7 +11,6 @@ export type CustomerProductControllerUpdateRequest = {
query: z.infer<typeof CustomerProductControllerUpdateQuerySchema>;
body: z.infer<typeof CustomerProductControllerUpdateBodySchema>;
context: InvocationContext;
request: HttpRequest;
};

const CustomerProductControllerUpdateQuerySchema = z.object({
Expand All @@ -33,12 +32,7 @@ export type CustomerProductControllerUpdateResponse = Awaited<
>;

export const CustomerProductControllerUpdate = _(
async ({
query,
body,
request,
context,
}: CustomerProductControllerUpdateRequest) => {
async ({ query, body, context }: CustomerProductControllerUpdateRequest) => {
const validateQuery =
CustomerProductControllerUpdateQuerySchema.parse(query);
const validateBody = CustomerProductControllerUpdateBodySchema.parse(body);
Expand All @@ -49,6 +43,7 @@ export const CustomerProductControllerUpdate = _(
);

await CustomerProductUpdateOrchestration(validateQuery, context);

return product;
}
);
2 changes: 1 addition & 1 deletion src/functions/customer/orchestrations/customer/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const orchestrator: df.OrchestrationHandler = function* (
yield context.df.callActivity(
updateArticleName,
activityType<typeof updateArticle>({
user,
customerId: user.customerId,
})
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { CustomerServiceGet } from "~/functions/customer/services/customer/get";
import { CustomerLocationServiceList } from "~/functions/customer/services/location/list";
import { ScheduleModel } from "~/functions/schedule";
import { User } from "~/functions/user";
import { shopifyRest } from "~/library/shopify/rest";

export const updateArticleName = "updateArticle";
export const updateArticle = async ({
user,
customerId,
}: {
user: User;
customerId: number;
}): Promise<RootObject> => {
const user = await CustomerServiceGet({ customerId });

const schedules = await ScheduleModel.find({
customerId: user.customerId,
});
Expand Down
57 changes: 57 additions & 0 deletions src/functions/customer/orchestrations/product/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { InvocationContext } from "@azure/functions";
import * as df from "durable-functions";
import { OrchestrationContext } from "durable-functions";
import { activityType } from "~/library/orchestration";
import {
updateArticle,
updateArticleName,
} from "../customer/update/update-article";
import { updatePrice, updatePriceName } from "./update/update-price";
import { updateProduct, updateProductName } from "./update/update-product";

const orchestrator: df.OrchestrationHandler = function* (
context: OrchestrationContext
) {
const input = context.df.getInput() as Input;

const productUpdated: Awaited<ReturnType<typeof updateProduct>> =
yield context.df.callActivity(
updateProductName,
activityType<typeof updateProduct>(input)
);

const priceUpdated: Awaited<ReturnType<typeof updatePrice>> =
yield context.df.callActivity(
updatePriceName,
activityType<typeof updatePrice>(input)
);

const article: Awaited<ReturnType<typeof updateArticle>> =
yield context.df.callActivity(
updateArticleName,
activityType<typeof updateArticle>(input)
);

return { article, productUpdated, priceUpdated };
};

df.app.orchestration("CustomerProductAddOrchestration", orchestrator);

type Input = { productId: number; customerId: number };

export const CustomerProductAddOrchestration = async (
input: Input,
context: InvocationContext
): Promise<string> => {
const client = df.getClient(context);
const instanceId: string = await client.startNew(
"CustomerProductAddOrchestration",
{
input,
}
);

context.log(`Started orchestration with ID = '${instanceId}'.`);

return instanceId;
};
58 changes: 58 additions & 0 deletions src/functions/customer/orchestrations/product/destroy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { InvocationContext } from "@azure/functions";
import * as df from "durable-functions";
import { OrchestrationContext } from "durable-functions";
import { ScheduleProduct } from "~/functions/schedule";
import { activityType } from "~/library/orchestration";
import {
destroyProductOption,
destroyProductOptionName,
} from "../product-options/destroy/destroy-option";
import { destroyProduct, destroyProductName } from "./destroy/destroy-product";

df.app.activity(destroyProductName, { handler: destroyProduct });

const orchestrator: df.OrchestrationHandler = function* (
context: OrchestrationContext
) {
const input = context.df.getInput() as Input;

for (const productOption of input.product.options || []) {
yield context.df.callActivity(
destroyProductOptionName,
activityType<typeof destroyProductOption>({
productOptionId: productOption.productId,
})
);
}

const productDestroyed: Awaited<ReturnType<typeof destroyProduct>> =
yield context.df.callActivity(
destroyProductName,
activityType<typeof destroyProduct>(input.product)
);

return { productDestroyed };
};

df.app.orchestration("CustomerProductDestroyOrchestration", orchestrator);

type Input = {
product: ScheduleProduct;
};

export const CustomerProductDestroyOrchestration = async (
input: Input,
context: InvocationContext
): Promise<string> => {
const client = df.getClient(context);
const instanceId: string = await client.startNew(
"CustomerProductDestroyOrchestration",
{
input,
}
);

context.log(`Started orchestration with ID = '${instanceId}'.`);

return instanceId;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { shopifyAdmin } from "~/library/shopify";
import { ProductOptionDestroyMutation } from "~/types/admin.generated";
import { destroyProduct, PRODUCT_DESTROY } from "./destroy-product";

require("~/library/jest/mongoose/mongodb.jest");

jest.mock("~/library/shopify", () => ({
shopifyAdmin: jest.fn().mockReturnValue({
request: jest.fn(),
}),
}));

const mockRequest = shopifyAdmin().request as jest.Mock;

const productDelete: ProductOptionDestroyMutation = {
productDelete: {
deletedProductId: `gid://shopify/Product/123`,
},
};

describe("CustomerProductDestroyOrchestration", () => {
beforeAll(async () => {
jest.clearAllMocks();
});

it("destroyProduct", async () => {
mockRequest.mockResolvedValueOnce({
data: {
productDelete: {
deletedProductId: {
id: "123",
},
},
},
});

await destroyProduct({
productId: 123,
});

expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenNthCalledWith(1, PRODUCT_DESTROY, {
variables: {
productId: `gid://shopify/Product/123`,
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { shopifyAdmin } from "~/library/shopify";

export const destroyProductName = "destroyProductName";
export const destroyProduct = async ({ productId }: { productId: number }) => {
const { data } = await shopifyAdmin().request(PRODUCT_DESTROY, {
variables: {
productId: `gid://shopify/Product/${productId}`,
},
});

return data?.productDelete?.deletedProductId;
};

export const PRODUCT_DESTROY = `#graphql
mutation productDestroy($productId: ID!) {
productDelete(input: {id: $productId}) {
deletedProductId
}
}
` as const;
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ describe("CustomerProductUpdateOrchestration", () => {
id: mockProductUpdate.productUpdate?.product?.id,
title: product.title,
descriptionHtml: product.descriptionHtml,
handle: product.productHandle,
metafields: [
{
id: product?.hideFromProfileMetafieldId,
Expand Down
Loading

0 comments on commit f2491dd

Please sign in to comment.