diff --git a/packages/medusa/src/loaders/__tests__/default.spec.ts b/packages/medusa/src/loaders/__tests__/default.spec.ts new file mode 100644 index 0000000000..dac47f5edb --- /dev/null +++ b/packages/medusa/src/loaders/__tests__/default.spec.ts @@ -0,0 +1,55 @@ +import { asValue, createContainer } from "awilix"; +import { MockRepository, MockManager } from "medusa-test-utils" +import { StoreServiceMock } from "../../services/__mocks__/store"; +import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile"; +import Logger from "../logger"; +import featureFlagsLoader from "../feature-flags"; +import { default as defaultLoader } from "../defaults" +import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel"; +import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider"; + +describe('default', () => { + describe('sales channel default', () => { + let featureFlagRouter + const container = createContainer() + + beforeAll(async () => { + featureFlagRouter = await featureFlagsLoader({ + featureFlags: { + sales_channels: true, + }, + }, Logger) + + container.register({ + storeService: asValue(StoreServiceMock), + currencyRepository: asValue(MockRepository()), + countryRepository: asValue(MockRepository()), + shippingProfileService: asValue(ShippingProfileServiceMock), + salesChannelService: asValue(SalesChannelServiceMock), + logger: asValue(Logger), + featureFlagRouter: asValue(featureFlagRouter), + manager: asValue(MockManager), + paymentProviders: asValue([]), + paymentProviderService: asValue(PaymentProviderServiceMock), + notificationProviders: asValue([]), + notificationService: asValue({ + registerInstalledProviders: jest.fn(), + }), + fulfillmentProviders: asValue([]), + fulfillmentProviderService: asValue({ + registerInstalledProviders: jest.fn(), + }), + taxProviders: asValue([]), + taxProviderService: asValue({ + registerInstalledProviders: jest.fn(), + }), + }) + }) + + it("should create a new default sales channel attach to the store", async () => { + await defaultLoader({ container }) + expect(SalesChannelServiceMock.createDefault).toHaveBeenCalledTimes(1) + expect(SalesChannelServiceMock.createDefault).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/medusa/src/loaders/defaults.ts b/packages/medusa/src/loaders/defaults.ts index b9b59afc5a..6d4d26ea43 100644 --- a/packages/medusa/src/loaders/defaults.ts +++ b/packages/medusa/src/loaders/defaults.ts @@ -9,11 +9,15 @@ import { FulfillmentProviderService, NotificationService, PaymentProviderService, + SalesChannelService, ShippingProfileService, - StoreService, TaxProviderService, + StoreService, + TaxProviderService, } from "../services" import { CurrencyRepository } from "../repositories/currency" import { AbstractTaxService } from "../interfaces" +import { FlagRouter } from "../utils/flag-router"; +import SalesChannelFeatureFlag from "./feature-flags/sales-channels"; const silentResolution = (container: AwilixContainer, name: string, logger: Logger): T | never | undefined => { try { @@ -49,7 +53,9 @@ export default async ({ container }: { container: AwilixContainer }): Promise("currencyRepository") const countryRepository = container.resolve("countryRepository") const profileService = container.resolve("shippingProfileService") + const salesChannelService = container.resolve("salesChannelService") const logger = container.resolve("logger") + const featureFlagRouter = container.resolve("featureFlagRouter") const entityManager = container.resolve("manager") @@ -97,7 +103,6 @@ export default async ({ container }: { container: AwilixContainer }): Promise { await storeService.withTransaction(manager).create() - const payProviders = silentResolution(container, "paymentProviders", logger) || [] const payIds = payProviders.map((p) => p.getIdentifier()) @@ -129,5 +134,10 @@ export default async ({ container }: { container: AwilixContainer }): Promise { return Promise.resolve() }), + registerInstalledProviders: jest.fn().mockImplementation(() => { + return Promise.resolve() + }), createSession: jest.fn().mockImplementation((providerId, cart) => { return Promise.resolve({ id: `${providerId}_session`, diff --git a/packages/medusa/src/services/__mocks__/sales-channel.js b/packages/medusa/src/services/__mocks__/sales-channel.js index 19dc51a985..a5cd66a013 100644 --- a/packages/medusa/src/services/__mocks__/sales-channel.js +++ b/packages/medusa/src/services/__mocks__/sales-channel.js @@ -27,6 +27,14 @@ export const SalesChannelServiceMock = { delete: jest.fn().mockImplementation((id, config) => { return Promise.resolve() }), + + createDefault: jest.fn().mockImplementation(() => { + return Promise.resolve({ + name: "sales channel 1 name", + description: "sales channel 1 description", + is_disabled: false, + }) + }) } const mock = jest.fn().mockImplementation(() => { diff --git a/packages/medusa/src/services/__mocks__/shipping-profile.js b/packages/medusa/src/services/__mocks__/shipping-profile.js index d4e3848136..c29a205d5d 100644 --- a/packages/medusa/src/services/__mocks__/shipping-profile.js +++ b/packages/medusa/src/services/__mocks__/shipping-profile.js @@ -16,12 +16,21 @@ export const profiles = { } export const ShippingProfileServiceMock = { + withTransaction: function () { + return this + }, update: jest.fn().mockImplementation(data => { return Promise.resolve() }), create: jest.fn().mockImplementation(data => { return Promise.resolve(data) }), + createDefault: jest.fn().mockImplementation(() => { + return Promise.resolve() + }), + createGiftCardDefault: jest.fn().mockImplementation(() => { + return Promise.resolve() + }), retrieve: jest.fn().mockImplementation(data => { if (data === IdMap.getId("default")) { return Promise.resolve(profiles.default) diff --git a/packages/medusa/src/services/__mocks__/store.js b/packages/medusa/src/services/__mocks__/store.js index e31d3c0a90..cf58e44349 100644 --- a/packages/medusa/src/services/__mocks__/store.js +++ b/packages/medusa/src/services/__mocks__/store.js @@ -7,6 +7,12 @@ export const store = { } export const StoreServiceMock = { + withTransaction: function () { + return this + }, + create: jest.fn().mockImplementation(data => { + return Promise.resolve(data) + }), addCurrency: jest.fn().mockImplementation(data => { return Promise.resolve() }), diff --git a/packages/medusa/src/services/__tests__/sales-channel.ts b/packages/medusa/src/services/__tests__/sales-channel.ts index 56d915e812..bfc7bf6f4d 100644 --- a/packages/medusa/src/services/__tests__/sales-channel.ts +++ b/packages/medusa/src/services/__tests__/sales-channel.ts @@ -1,9 +1,10 @@ import { IdMap, MockManager, MockRepository } from "medusa-test-utils" import SalesChannelService from "../sales-channel" import { EventBusServiceMock } from "../__mocks__/event-bus" -import { EventBusService } from "../index" +import { EventBusService, StoreService } from "../index" import { FindConditions, FindOneOptions } from "typeorm" import { SalesChannel } from "../../models" +import { store, StoreServiceMock } from "../__mocks__/store"; describe("SalesChannelService", () => { const salesChannelData = { @@ -27,17 +28,76 @@ describe("SalesChannelService", () => { }) } ), - save: (salesChannel) => Promise.resolve(salesChannel), + create: jest.fn().mockImplementation((data) => data), + save: (salesChannel) => Promise.resolve({ + id: IdMap.getId("sales_channel_1"), + ...salesChannel + }), softRemove: jest.fn().mockImplementation((id: string): any => { return Promise.resolve() }), }) + describe("create default", async () => { + const salesChannelService = new SalesChannelService({ + manager: MockManager, + eventBusService: EventBusServiceMock as unknown as EventBusService, + salesChannelRepository: salesChannelRepositoryMock, + storeService: StoreServiceMock as unknown as StoreService + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should call the save method if the store does not have a default sales channel", async () => { + await salesChannelService.createDefault() + + expect(salesChannelRepositoryMock.save).toHaveBeenCalledTimes(1) + expect(salesChannelRepositoryMock.save).toHaveBeenCalledWith({ + description: "Created by Medusa", + name: "Default Sales Channel", + is_disabled: false, + }) + }) + + it("should return the default sales channel if it already exists", async () => { + const localSalesChannelService = new SalesChannelService({ + manager: MockManager, + eventBusService: EventBusServiceMock as unknown as EventBusService, + salesChannelRepository: salesChannelRepositoryMock, + storeService: { + ...StoreServiceMock, + retrieve: jest.fn().mockImplementation(() => { + return Promise.resolve({ + ...store, + default_sales_channel_id: IdMap.getId("sales_channel_1"), + default_sales_channel: { + id: IdMap.getId("sales_channel_1"), + ...salesChannelData, + } + }) + }) + } as any + }) + + const salesChannel = await localSalesChannelService.createDefault() + + expect(salesChannelRepositoryMock.save).toHaveBeenCalledTimes(0) + expect(salesChannelRepositoryMock.save).not.toHaveBeenCalledTimes(1) + expect(salesChannel).toEqual({ + id: IdMap.getId("sales_channel_1"), + ...salesChannelData, + }) + }) + }) + describe("retrieve", () => { const salesChannelService = new SalesChannelService({ manager: MockManager, eventBusService: EventBusServiceMock as unknown as EventBusService, salesChannelRepository: salesChannelRepositoryMock, + storeService: StoreServiceMock as unknown as StoreService }) beforeEach(() => { @@ -67,6 +127,7 @@ describe("SalesChannelService", () => { manager: MockManager, eventBusService: EventBusServiceMock as unknown as EventBusService, salesChannelRepository: salesChannelRepositoryMock, + storeService: StoreServiceMock as unknown as StoreService }) const update = { @@ -100,7 +161,8 @@ describe("SalesChannelService", () => { const salesChannelService = new SalesChannelService({ manager: MockManager, eventBusService: EventBusServiceMock as unknown as EventBusService, - salesChannelRepository: salesChannelRepositoryMock + salesChannelRepository: salesChannelRepositoryMock, + storeService: StoreServiceMock as unknown as StoreService }) beforeEach(() => { diff --git a/packages/medusa/src/services/sales-channel.ts b/packages/medusa/src/services/sales-channel.ts index 72b432989b..b358c0f24b 100644 --- a/packages/medusa/src/services/sales-channel.ts +++ b/packages/medusa/src/services/sales-channel.ts @@ -11,12 +11,13 @@ import { } from "../types/sales-channels" import EventBusService from "./event-bus" import { buildQuery } from "../utils" -import { PostgresError } from "../utils/exception-formatter" +import StoreService from "./store" type InjectedDependencies = { salesChannelRepository: typeof SalesChannelRepository eventBusService: EventBusService manager: EntityManager + storeService: StoreService } class SalesChannelService extends TransactionBaseService { @@ -31,11 +32,13 @@ class SalesChannelService extends TransactionBaseService { protected readonly salesChannelRepository_: typeof SalesChannelRepository protected readonly eventBusService_: EventBusService + protected readonly storeService_: StoreService constructor({ salesChannelRepository, eventBusService, manager, + storeService, }: InjectedDependencies) { // eslint-disable-next-line prefer-rest-params super(arguments[0]) @@ -43,6 +46,7 @@ class SalesChannelService extends TransactionBaseService { this.manager_ = manager this.salesChannelRepository_ = salesChannelRepository this.eventBusService_ = eventBusService + this.storeService_ = storeService } /** @@ -170,6 +174,36 @@ class SalesChannelService extends TransactionBaseService { }) }) } + + /** + * Creates a default sales channel, if this does not already exist. + * @return the sales channel + */ + async createDefault(): Promise { + return this.atomicPhase_(async (transactionManager) => { + const store = await this.storeService_ + .withTransaction(transactionManager) + .retrieve({ + relations: ["default_sales_channel"], + }) + + if (store.default_sales_channel_id) { + return store.default_sales_channel + } + + const defaultSalesChannel = await this.create({ + description: "Created by Medusa", + name: "Default Sales Channel", + is_disabled: false, + }) + + await this.storeService_.withTransaction(transactionManager).update({ + default_sales_channel_id: defaultSalesChannel.id, + }) + + return defaultSalesChannel + }) + } } export default SalesChannelService diff --git a/packages/medusa/src/types/store.ts b/packages/medusa/src/types/store.ts index 0103a66fa8..87ec3f316f 100644 --- a/packages/medusa/src/types/store.ts +++ b/packages/medusa/src/types/store.ts @@ -6,4 +6,5 @@ export type UpdateStoreInput = { default_currency_code?: string currencies?: string[] metadata?: Record + default_sales_channel_id?: string }