import { IFulfillmentModuleService, StockLocationDTO } from "@medusajs/types" import { Modules } from "@medusajs/utils" import { medusaIntegrationTestRunner } from "@medusajs/test-utils" import { createAdminUser } from "../../../helpers/create-admin-user" import { generateCreateFulfillmentData, generateCreateShippingOptionsData, setupFullDataFulfillmentStructure, } from "../fixtures" jest.setTimeout(100000) const env = { MEDUSA_FF_MEDUSA_V2: true } 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", }) }) }) }, })