Files
medusa-store/integration-tests/modules/__tests__/fulfillment/index.spec.ts
Carlos R. L. Rodrigues e413cfefc2 feat(utils): define file config (#13283)
** What
 - Allow auto-loaded Medusa files to export a config object.
 - Currently supports isDisabled to control loading.
 - new instance `FeatureFlag` exported by `@medusajs/framework/utils`
 - `feature-flags` is now a supported folder for medusa projects, modules, providers and plugins. They will be loaded and added to `FeatureFlag`

** Why
 - Enables conditional loading of routes, migrations, jobs, subscribers, workflows, and other files based on feature flags.

```ts
// /src/feature-flags

import { FlagSettings } from "@medusajs/framework/feature-flags"

const CustomFeatureFlag: FlagSettings = {
  key: "custom_feature",
  default_val: false,
  env_key: "FF_MY_CUSTOM_FEATURE",
  description: "Enable xyz",
}

export default CustomFeatureFlag
```

```ts
// /src/modules/my-custom-module/migration/Migration20250822135845.ts

import { FeatureFlag } from "@medusajs/framework/utils"

export class Migration20250822135845 extends Migration {
  override async up(){ }
  override async down(){ }
}

defineFileConfig({
  isDisabled: () => !FeatureFlag.isFeatureEnabled("custom_feature")
})
```
2025-08-26 12:22:30 +00:00

319 lines
10 KiB
TypeScript

import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { IFulfillmentModuleService, StockLocationDTO } from "@medusajs/types"
import { Modules } from "@medusajs/utils"
import { createAdminUser } from "../../../helpers/create-admin-user"
import {
generateCreateFulfillmentData,
generateCreateShippingOptionsData,
setupFullDataFulfillmentStructure,
} from "../fixtures"
jest.setTimeout(100000)
const env = {}
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
const providerId = "manual_test-provider"
medusaIntegrationTestRunner({
env,
testSuite: ({ getContainer, api, dbConnection }) => {
let service: IFulfillmentModuleService
let container
let location: StockLocationDTO
beforeAll(() => {
container = getContainer()
service = container.resolve(Modules.FULFILLMENT)
})
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, container)
const stockLocationService = container.resolve(Modules.STOCK_LOCATION)
location = await stockLocationService.createStockLocations({
name: "Test Location",
address: {
address_1: "Test Address",
address_2: "tttest",
city: "Test City",
country_code: "us",
postal_code: "12345",
metadata: { email: "test@mail.com" },
},
metadata: { custom_location: "yes" },
})
})
/**
* The test runner run both the medusa migrations as well as the modules
* migrations. In order to ensure the backward compatibility
* of the migration works, we will create a full data structure.
*/
describe("Fulfillment module migrations backward compatibility", () => {
it("should allow to create a full data structure after the backward compatible migration have run on top of the medusa v1 database", async () => {
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})
const fulfillmentSets = await service.listFulfillmentSets(
{},
{
relations: [
"service_zones.geo_zones",
"service_zones.shipping_options.shipping_profile",
"service_zones.shipping_options.provider",
"service_zones.shipping_options.type",
"service_zones.shipping_options.rules",
"service_zones.shipping_options.fulfillments.labels",
"service_zones.shipping_options.fulfillments.items",
"service_zones.shipping_options.fulfillments.delivery_address",
],
}
)
expect(fulfillmentSets).toHaveLength(1)
let fulfillmentSet = fulfillmentSets[0]
expect(fulfillmentSet.service_zones).toHaveLength(1)
let serviceZone = fulfillmentSet.service_zones[0]
expect(serviceZone.geo_zones).toHaveLength(1)
expect(serviceZone.shipping_options).toHaveLength(1)
let geoZone = serviceZone.geo_zones[0]
let shippingOption = serviceZone.shipping_options[0]
expect(!!shippingOption.shipping_profile.deleted_at).toEqual(false)
expect(shippingOption.fulfillments).toHaveLength(1)
expect(shippingOption.rules).toHaveLength(1)
let fulfillment = shippingOption.fulfillments[0]
expect(fulfillment.labels).toHaveLength(1)
expect(fulfillment.items).toHaveLength(1)
})
})
describe("POST /admin/fulfillments/:id/cancel", () => {
it("should throw an error when id is not found", async () => {
const error = await api
.post(`/admin/fulfillments/does-not-exist/cancel`, {}, adminHeaders)
.catch((e) => e)
expect(error.response.status).toEqual(404)
expect(error.response.data).toEqual({
type: "not_found",
message: "Fulfillment with id: does-not-exist was not found",
})
})
it("should cancel a fulfillment", async () => {
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})
const [fulfillment] = await service.listFulfillments()
const response = await api.post(
`/admin/fulfillments/${fulfillment.id}/cancel`,
{},
adminHeaders
)
expect(response.status).toEqual(200)
const canceledFulfillment = await service.retrieveFulfillment(
fulfillment.id
)
expect(canceledFulfillment.canceled_at).toBeTruthy()
})
})
describe("POST /admin/fulfillments", () => {
it("should create a fulfillment", async () => {
const shippingProfile = await service.createShippingProfiles({
name: "test",
type: "default",
})
const fulfillmentSet = await service.createFulfillmentSets({
name: "test",
type: "test-type",
})
const serviceZone = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const shippingOption = await service.createShippingOptions(
generateCreateShippingOptionsData({
provider_id: providerId,
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
})
)
const data = generateCreateFulfillmentData({
location_id: location.id,
provider_id: providerId,
shipping_option_id: shippingOption.id,
order_id: "order_123",
})
const response = await api
.post(`/admin/fulfillments`, data, adminHeaders)
.catch((e) => e)
expect(response.status).toEqual(200)
expect(response.data.fulfillment).toEqual(
expect.objectContaining({
id: expect.any(String),
location_id: location.id,
packed_at: null,
shipped_at: null,
delivered_at: null,
canceled_at: null,
created_by: expect.any(String),
provider_id: "manual_test-provider",
delivery_address: expect.objectContaining({
address_1: expect.any(String),
address_2: expect.any(String),
city: expect.any(String),
country_code: expect.any(String),
province: expect.any(String),
postal_code: expect.any(String),
}),
items: [
expect.objectContaining({
id: expect.any(String),
title: expect.any(String),
sku: expect.any(String),
barcode: expect.any(String),
raw_quantity: {
value: "1",
precision: 20,
},
quantity: 1,
}),
],
labels: [
expect.objectContaining({
id: expect.any(String),
tracking_number: expect.any(String),
tracking_url: expect.any(String),
label_url: expect.any(String),
}),
],
})
)
})
})
describe("POST /admin/fulfillments/:id/shipment", () => {
it("should throw an error when id is not found", async () => {
const error = await api
.post(
`/admin/fulfillments/does-not-exist/shipment`,
{
labels: [
{
tracking_number: "test-tracking-number",
tracking_url: "test-tracking-url",
label_url: "test-label-url",
},
],
},
adminHeaders
)
.catch((e) => e)
expect(error.response.status).toEqual(404)
expect(error.response.data).toEqual({
type: "not_found",
message: "Fulfillment with id: does-not-exist was not found",
})
})
it("should update a fulfillment to be shipped", async () => {
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})
const [fulfillment] = await service.listFulfillments()
const response = await api.post(
`/admin/fulfillments/${fulfillment.id}/shipment`,
{
labels: [
{
tracking_number: "test-tracking-number",
tracking_url: "test-tracking-url",
label_url: "test-label-url",
},
],
},
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.fulfillment).toEqual(
expect.objectContaining({
id: fulfillment.id,
shipped_at: expect.any(String),
marked_shipped_by: expect.any(String),
labels: [
expect.objectContaining({
id: expect.any(String),
tracking_number: "test-tracking-number",
tracking_url: "test-tracking-url",
label_url: "test-label-url",
}),
],
})
)
})
it("should throw error when already shipped", async () => {
await setupFullDataFulfillmentStructure(service, {
providerId,
locationId: location.id,
})
const [fulfillment] = await service.listFulfillments()
await service.updateFulfillment(fulfillment.id, {
shipped_at: new Date(),
})
const error = await api
.post(
`/admin/fulfillments/${fulfillment.id}/shipment`,
{
labels: [
{
tracking_number: "test-tracking-number",
tracking_url: "test-tracking-url",
label_url: "test-label-url",
},
],
},
adminHeaders
)
.catch((e) => e)
expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
type: "not_allowed",
message: "Shipment has already been created",
})
})
})
},
})