Skip to content

Commit

Permalink
feat: add support for as-associate endpoints
Browse files Browse the repository at this point in the history
This implements the carts and orders endpoints via the as-associate
endpoints. It doesn’t do any permission checking for now, just proxies
the requests to the existing code.

In the future we should filter the responses atleast on the business
units
  • Loading branch information
mvantellingen committed Dec 10, 2024
1 parent ca97a89 commit a7166d7
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/lemon-monkeys-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@labdigital/commercetools-mock": minor
---

Support as-associate endpoints for carts and orders
6 changes: 1 addition & 5 deletions src/ctMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { OAuth2Server } from "./oauth/server";
import { ProjectAPI } from "./projectAPI";
import type { AbstractStorage } from "./storage";
import { InMemoryStorage } from "./storage";
import type { Services } from "./types";

// Services
import { warnDeprecation } from "./deprecation";
Expand Down Expand Up @@ -56,15 +55,12 @@ export class CommercetoolsMock {

private _mswServer: SetupServer | undefined = undefined;

private _services: Services | null;

private _repositories: RepositoryMap | null;

private _projectService?: ProjectService;

constructor(options: Partial<CommercetoolsMockOptions> = {}) {
this.options = { ...DEFAULT_OPTIONS, ...options };
this._services = null;
this._repositories = null;
this._projectService = undefined;

Expand Down Expand Up @@ -154,7 +150,7 @@ export class CommercetoolsMock {
}

// Register the rest api services in the router
this._services = createServices(projectRouter, this._repositories);
createServices(projectRouter, this._repositories);
this._projectService = new ProjectService(
projectRouter,
this._repositories.project as ProjectRepository,
Expand Down
5 changes: 5 additions & 0 deletions src/repositories/as-associate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CartRepository } from "./cart";
import { OrderRepository } from "./order";

export class AsAssociateOrderRepository extends OrderRepository {}
export class AsAssociateCartRepository extends CartRepository {}
8 changes: 8 additions & 0 deletions src/repositories/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ProductTailoringRepository } from "~src/repositories/product-tailoring";
import type { AbstractStorage } from "../storage";
import {
AsAssociateCartRepository,
AsAssociateOrderRepository,
} from "./as-associate";
import { AssociateRoleRepository } from "./associate-role";
import { AttributeGroupRepository } from "./attribute-group";
import { BusinessUnitRepository } from "./business-unit";
Expand Down Expand Up @@ -41,6 +45,10 @@ import { ZoneRepository } from "./zone";
export type RepositoryMap = ReturnType<typeof createRepositories>;

export const createRepositories = (storage: AbstractStorage) => ({
"as-associate": {
cart: new AsAssociateCartRepository(storage),
order: new AsAssociateOrderRepository(storage),
},
"associate-role": new AssociateRoleRepository(storage),
"attribute-group": new AttributeGroupRepository(storage),
"business-unit": new BusinessUnitRepository(storage),
Expand Down
33 changes: 33 additions & 0 deletions src/services/as-associate-cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Router } from "express";
import type { CartRepository } from "../repositories/cart";
import AbstractService from "./abstract";

export class AsAssociateCartService extends AbstractService {
public repository: CartRepository;

constructor(parent: Router, repository: CartRepository) {
super(parent);
this.repository = repository;
}

getBasePath() {
return "carts";
}

registerRoutes(parent: Router) {
const basePath = this.getBasePath();
const router = Router({ mergeParams: true });

this.extraRoutes(router);

router.get("/", this.get.bind(this));
router.get("/:id", this.getWithId.bind(this));

router.delete("/:id", this.deleteWithId.bind(this));

router.post("/", this.post.bind(this));
router.post("/:id", this.postWithId.bind(this));

parent.use(`/${basePath}`, router);
}
}
64 changes: 64 additions & 0 deletions src/services/as-associate-order.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Order } from "@commercetools/platform-sdk";
import assert from "assert";
import supertest from "supertest";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { CommercetoolsMock } from "../index";

describe("Order Query", () => {
const ctMock = new CommercetoolsMock();
let order: Order | undefined;
const projectKey = "dummy";
const customerId = "5fac8fca-2484-4b14-a1d1-cfdce2f8d3c4";
const businessUnitKey = "business-unit";

beforeEach(async () => {
let response = await supertest(ctMock.app)
.post(
`/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/carts`,
)
.send({
currency: "EUR",
custom: {
type: {
key: "my-cart",
},
fields: {
description: "example description",
},
},
});
expect(response.status).toBe(201);
const cart = response.body;

response = await supertest(ctMock.app)
.post(
`/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/orders`,
)
.send({
cart: {
typeId: "cart",
id: cart.id,
},
orderNumber: "foobar",
});
expect(response.status).toBe(201);
order = response.body;
});

afterEach(() => {
ctMock.clear();
});

test("no filter", async () => {
assert(order, "order not created");

const response = await supertest(ctMock.app).get(
`/${projectKey}/as-associate/${customerId}/in-business-unit/key=${businessUnitKey}/orders`,
);
expect(response.status).toBe(200);
expect(response.body.count).toBe(1);
expect(response.body.total).toBe(1);
expect(response.body.offset).toBe(0);
expect(response.body.limit).toBe(20);
});
});
33 changes: 33 additions & 0 deletions src/services/as-associate-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Router } from "express";
import type { MyOrderRepository } from "../repositories/my-order";
import AbstractService from "./abstract";

export class AsAssociateOrderService extends AbstractService {
public repository: MyOrderRepository;

constructor(parent: Router, repository: MyOrderRepository) {
super(parent);
this.repository = repository;
}

getBasePath() {
return "orders";
}

registerRoutes(parent: Router) {
const basePath = this.getBasePath();
const router = Router({ mergeParams: true });

this.extraRoutes(router);

router.get("/", this.get.bind(this));
router.get("/:id", this.getWithId.bind(this));

router.delete("/:id", this.deleteWithId.bind(this));

router.post("/", this.post.bind(this));
router.post("/:id", this.postWithId.bind(this));

parent.use(`/${basePath}`, router);
}
}
34 changes: 34 additions & 0 deletions src/services/as-associate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Router } from "express";
import type {
AsAssociateCartRepository,
AsAssociateOrderRepository,
} from "~src/repositories/as-associate";
import { AsAssociateCartService } from "./as-associate-cart";
import { AsAssociateOrderService } from "./as-associate-order";

type Repositories = {
cart: AsAssociateCartRepository;
order: AsAssociateOrderRepository;
};

export class AsAssociateService {
router: Router;

subServices: {
cart: AsAssociateCartService;
order: AsAssociateOrderService;
};

constructor(parent: Router, repositories: Repositories) {
this.router = Router({ mergeParams: true });

this.subServices = {
order: new AsAssociateOrderService(this.router, repositories.order),
cart: new AsAssociateCartService(this.router, repositories.cart),
};
parent.use(
"/as-associate/:associateId/in-business-unit/key=:businessUnitId",
this.router,
);
}
}
2 changes: 2 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { createRepositories } from "../repositories";
import { AsAssociateService } from "./as-associate";
import { AssociateRoleServices } from "./associate-roles";
import { AttributeGroupService } from "./attribute-group";
import { BusinessUnitServices } from "./business-units";
Expand Down Expand Up @@ -41,6 +42,7 @@ export const createServices = (
repos: ReturnType<typeof createRepositories>,
) => ({
"associate-role": new AssociateRoleServices(router, repos["associate-role"]),
"as-associate": new AsAssociateService(router, repos["as-associate"]),
"business-unit": new BusinessUnitServices(router, repos["business-unit"]),
"category": new CategoryServices(router, repos["category"]),
"cart": new CartService(router, repos["cart"], repos["order"]),
Expand Down
5 changes: 0 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type * as ctp from "@commercetools/platform-sdk";
import type { RepositoryMap } from "./repositories";
import type AbstractService from "./services/abstract";

export const isType = <T>(x: T) => x;

Expand All @@ -17,10 +16,6 @@ export type ServiceTypes =
| "my-customer"
| "my-business-unit";

export type Services = Partial<{
[index in ServiceTypes]: AbstractService;
}>;

export type ResourceType = keyof ResourceMap & keyof RepositoryMap;

export type ResourceMap = {
Expand Down

0 comments on commit a7166d7

Please sign in to comment.