Merge remote-tracking branch 'origin/develop' into feat/meilisearch-plugin

This commit is contained in:
Sebastian Rindom committed 2021-09-29 15:20:22 +02:00
commit 10c87e8d5a
271 files changed
+16116 -5647

No files matched your search

+17 -31
View File
@@ -49,24 +49,6 @@ Medusa is an open-source headless commerce engine that enables developers to cre
After these four steps and only a couple of minutes, you now have a complete commerce engine running locally. You may now explore [the documentation](https://docs.medusa-commerce.com/api) to learn how to interact with the Medusa API. You may also add [plugins](https://github.com/medusajs/medusa/tree/master/packages) to your Medusa store by specifying them in your `medusa-config.js` file.
## ⭐️ Features
Medusa comes with a set of building blocks that allow you to create amazing digital commerce experiences, below is a list of some of the features that Medusa come with out of the box:
- **Headless**: Medusa is a highly customizable commerce API which means that you may use any presentation layer such as a website, app, chat bots, etc.
- **Regions** allow you to specify currencies, payment providers, shipping providers, tax rates and more for one or more countries for truly international sales.
- **Orders** come with all the functionality necessary to perform powerful customer service operations with ease.
- **Carts** allow customers to collect products for purchase, add shipping details and complete payments.
- **Products** come with relevant fields for customs, stock keeping and sales. Medusa supports multiple options and unlimited variants.
- **Swaps** allow customers to exchange products after purchase (e.g. for incorrect sizes). Accounting, payment and fulfillment plugins handle all the tedious work for you for automated customer service.
- **Claims** can be created if customers experience problems with one of their products. Plugins make sure to automate sending out replacements, handling refunds and collecting valuable data for analysis.
- **Returns** allow customers to send back products and can be configured to function in a 100% automated flow through accounting and payment plugins.
- **Fulfillment API** makes it easy to integrate with any fulfillment provider by creating fulfillment plugins, check the `/packages` directory for a full list of plugins.
- **Payments API** makes it easy to integrate with any payment provider by creatingn payment plugins, we already support Stripe, Paypal and Klarna.
- **Notification API** allow integrations with email providers, chat bots, Slack channels etc.
- **Customer Login** to give customers a way of managing their data, viewing their orders and saving payment details.
- **Shipping Options & Profiles** enable powerful rules for free shipping limits, multiple fulfillment methods and more.
- **Medusa's Plugin Architecture** makes it intuitive and easy to manage your integrations, switch providers and grow with ease.
- **Customization** is supported for those special use cases that all the other ecommerce platforms can't accommodate.
## 🛒 Setting up a storefront for your Medusa project
Medusa is a headless commerce engine which means that it can be used for any type of digital commerce experience - you may use it as the backend for an app, a voice application, social commerce experiences or a traditional e-commerce website, you may even want to integrate Medusa into your own software to enable commerce functionality. All of these are use cases that Medusa supports - to learn more read the documentation or reach out.
@@ -84,19 +66,23 @@ To provide a quick way to get you started with a storefront install one of our t
With your starter and your Medusa store running you can open http://localhost:8000 (for Gatsby) or http://localhost:3000 (for Nextjs) in your browser and view the products in your store, build a cart, add shipping details and pay and complete an order.
## ☁️ Linking development to Medusa Cloud
With your project in local development you can link your Medusa instance to Medusa Cloud - this will allow you to manage your store, view orders and test out the amazing functionalities that you are building. Linking your project to Medusa Cloud requires that you have a Medusa Cloud account.
1. **Authenticate your CLI with Medusa Cloud:**
```
medusa login
```
2. **Link project**
```
medusa link --develop
```
You can now navigate to Orders in Medusa Cloud to view the orders in your local Medusa project, just like you would if your store was running in production.
## ⭐️ Features
Medusa comes with a set of building blocks that allow you to create amazing digital commerce experiences, below is a list of some of the features that Medusa come with out of the box:
- **Headless**: Medusa is a highly customizable commerce API which means that you may use any presentation layer such as a website, app, chatbots, etc.
- **Regions** allow you to specify currencies, payment providers, shipping providers, tax rates and more for one or more countries for truly international sales.
- **Orders** come with all the functionality necessary to perform powerful customer service operations with ease.
- **Carts** allow customers to collect products for purchase, add shipping details and complete payments.
- **Products** come with relevant fields for customs, stock keeping and sales. Medusa supports multiple options and unlimited variants.
- **Swaps** allow customers to exchange products after purchase (e.g. for incorrect sizes). Accounting, payment and fulfillment plugins handle all the tedious work for you for automated customer service.
- **Claims** can be created if customers experience problems with one of their products. Plugins make sure to automate sending out replacements, handling refunds and collecting valuable data for analysis.
- **Returns** allow customers to send back products and can be configured to function in a 100% automated flow through accounting and payment plugins.
- **Fulfillment API** makes it easy to integrate with any fulfillment provider by creating fulfillment plugins, check the `/packages` directory for a full list of plugins.
- **Payments API** makes it easy to integrate with any payment provider by creating payment plugins, we already support Stripe, Paypal and Klarna.
- **Notification API** allow integrations with email providers, chatbots, Slack channels, etc.
- **Customer Login** to give customers a way of managing their data, viewing their orders and saving payment details.
- **Shipping Options & Profiles** enable powerful rules for free shipping limits, multiple fulfillment methods and more.
- **Medusa's Plugin Architecture** makes it intuitive and easy to manage your integrations, switch providers and grow with ease.
- **Customization** is supported for those special use cases that all the other e-commerce platforms can't accommodate.
## Database support
In production Medusa requires Postgres and Redis, but SQLite is supported for development and testing purposes. If you plan on using Medusa for a project it is recommended that you install Postgres and Redis on your dev machine.
+16 -16
View File
@@ -1,34 +1,34 @@
const path = require("path");
const express = require("express");
const getPort = require("get-port");
const importFrom = require("import-from");
const path = require("path")
const express = require("express")
const getPort = require("get-port")
const importFrom = require("import-from")
const initialize = async () => {
const app = express();
const app = express()
const cwd = process.cwd();
const loaders = importFrom(cwd, "@medusajs/medusa/dist/loaders").default;
const cwd = process.cwd()
const loaders = importFrom(cwd, "@medusajs/medusa/dist/loaders").default
const { dbConnection } = await loaders({
directory: path.resolve(process.cwd()),
expressApp: app,
});
})
const PORT = await getPort();
const PORT = await getPort()
return {
db: dbConnection,
app,
port: PORT,
};
};
}
}
const setup = async () => {
const { app, port } = await initialize();
const { app, port } = await initialize()
app.listen(port, (err) => {
process.send(port);
});
};
process.send(port)
})
}
setup();
setup()
+22 -22
View File
@@ -1,26 +1,26 @@
const { dropDatabase, createDatabase } = require("pg-god");
const { createConnection } = require("typeorm");
const { dropDatabase, createDatabase } = require("pg-god")
const { createConnection } = require("typeorm")
const path = require("path");
const path = require("path")
const DbTestUtil = {
db_: null,
setDb: function (connection) {
this.db_ = connection;
this.db_ = connection
},
clear: function () {
return this.db_.synchronize(true);
return this.db_.synchronize(true)
},
shutdown: async function () {
await this.db_.close();
return dropDatabase({ databaseName });
await this.db_.close()
return dropDatabase({ databaseName })
},
};
}
const instance = DbTestUtil;
const instance = DbTestUtil
module.exports = {
initDb: async function ({ cwd }) {
@@ -33,19 +33,19 @@ module.exports = {
`dist`,
`migrations`
)
);
)
const databaseName = "medusa-fixtures";
await createDatabase({ databaseName });
const databaseName = "medusa-fixtures"
await createDatabase({ databaseName })
const connection = await createConnection({
type: "postgres",
url: "postgres://localhost/medusa-fixtures",
migrations: [`${migrationDir}/*.js`],
});
})
await connection.runMigrations();
await connection.close();
await connection.runMigrations()
await connection.close()
const modelsLoader = require(path.join(
cwd,
@@ -55,19 +55,19 @@ module.exports = {
`dist`,
`loaders`,
`models`
)).default;
)).default
const entities = modelsLoader({}, { register: false });
const entities = modelsLoader({}, { register: false })
const dbConnection = await createConnection({
type: "postgres",
url: "postgres://localhost/medusa-fixtures",
entities,
});
})
instance.setDb(dbConnection);
return dbConnection;
instance.setDb(dbConnection)
return dbConnection
},
useDb: function () {
return instance;
return instance
},
};
}
+163
View File
@@ -0,0 +1,163 @@
---
title: Carts in Medusa
---
# Carts in Medusa
In Medusa a Cart serves the purpose of collecting the information needed to create an Order, including what products to purchase, what address to send the products to and which payment method the purchase will be processed by.
To create a cart using the `@medusajs/medusa-js` SDK you can use:
```javascript
const client = new Medusa({ baseUrl: "http://localhost:9000" })
const { cart } = await client.carts.create()
```
A Cart will always belong to a Region and you may provide a `region_id` upon Cart creation. If no `region_id` is specified Medusa will assign the Cart to a random Region. Regions specify information about how the Cart should be taxed, what currency the Cart should be paid with and what payment and fulfillment options will be available at checkout. Below are some of the properties that can be found on the Cart response. For a full example of a Cart response [check our fixtures](https://github.com/medusajs/medusa/blob/docs/api/docs/api/fixtures/store/GetCartsCart.json).
```json
"cart": {
"id": "cart_01FEWZSRFWT8QWMHJ7ZCPRP3BZ",
"email": null,
"billing_address": null,
"shipping_address": null,
"items": [ ... ],
"region": {
"id": "reg_01FEWZSRD7HVHBSQRC4KYMG5XM",
"name": "United States",
"currency_code": "usd",
"tax_rate": "0",
...
},
"discounts": [],
"gift_cards": [],
"customer_id": null,
"payment_sessions": [],
"payment": null,
"shipping_methods": [],
"type": "default",
"metadata": null,
"shipping_total": 0,
"discount_total": 0,
"tax_total": 0,
"gift_card_total": 0,
"subtotal": 1000,
"total": 1000,
...
}
```
## Adding products to the Cart
Customers can add products to the Cart in order to start gathering the items that will eventually be purchased. In Medusa adding a product to a Cart will result in a _Line Item_ being generated. To add a product using the SDK use:
```javascript
const { cart } = await client.carts.lineItems.create(cartId, {
variant_id: "[id-of-variant-to-add]",
quantity: 1,
})
```
The resulting response will look something like this:
```json
{
"cart": {
"id": "cart_01FEWZSRFWT8QWMHJ7ZCPRP3BZ",
"items": [
{
"id": "item_01FEWZSRMBAN85SKPCRMM30N6W",
"cart_id": "cart_01FEWZSRFWT8QWMHJ7ZCPRP3BZ",
"title": "Basic Tee",
"description": "Small",
"thumbnail": null,
"is_giftcard": false,
"should_merge": true,
"allow_discounts": true,
"has_shipping": false,
"unit_price": 1000,
"variant": {
"id": "variant_01FEWZSRDNWABVFZTZ21JWKHRG",
"title": "Small",
"product_id": "prod_01FEWZSRDHDDSHQV6ATG6MS2MF",
"sku": null,
"barcode": null,
"ean": null,
"upc": null,
"allow_backorder": false,
"hs_code": null,
"origin_country": null,
"mid_code": null,
"material": null,
"weight": null,
"length": null,
"height": null,
"width": null,
"metadata": null,
...
},
"quantity": 1,
"metadata": {},
...
}
],
...
}
}
```
The properties stored on a Line Item are useful for explaining and displaying the contents of the Cart. For example, Line Items can have a thumbnail assigned which can be used to display a pack shot of the product that is being purchased, a title to show name the products in the cart and a description to give further details about the product. By default the Line Item will be generated with properties inherited from the Product that is being added to the Cart, but the behaviour can be customized for other purposes as well.
## Adding Customer information to a Cart
After adding products to the Cart, you should gather information about where to send the products, this is done using the `update` method in the SDK.
```javascript
const { cart } = await client.carts.update(cartId, {
email: "jane.doe@mail.com",
shipping_address: {
first_name: "Jane",
last_name: "Doe",
address_1: "4242 Hollywood Dr",
postal_code: "12345",
country_code: "us",
city: "Los Angeles",
region: "CA",
},
})
```
Note that the country code in the shipping address must be the country code for one of the countries in a Region - otherwise this method will fail.
## Initializing Payment Sessions
In Medusa payments are handled through the long lived entities called _Payment Sessions_. Payment Sessions cary provider specific data that can later be used to authorize the payments, which is the step required before an order can be created. The SDK provides a `createPaymentSessions` method that can be used to initialize the payment sessions with the Payment Providers available in the Region.
```javascript
const { cart } = await client.carts.createPaymentSessions(cartId)
```
You can read more about Payment Sessions in our [guide to checkouts](https://docs.medusa-commerce.com/guides/checkouts).
## Changing the Cart region
To update the Region that the cart belongs to you should also use the `update` method from the SDK.
```javascript
const { cart } = await client.carts.update(cartId, {
region_id: "[id-of-region-to-switch-to]",
})
```
When changing the Cart region you should be aware of a couple of things:
- If switching to a Region with a different currency the line item prices and cart totals will change
- If switching to a Region with a different tax rate prices and totals will change
- If switching to a Region serving only one country the `shipping_address.country_code` will automatically be set to that country
- If the Cart already has initialized payment sessions all of these will be canceled and a new call to `createPaymentSessions` will have to be made
## What's next?
Carts are at the core of the shopping process in Medusa and provide all the necessary functionality to gather products for purchase. If you want to read a more detailed guide about how to complete checkouts please go to our [Checkout Guide](https://docs.medusa-commerce.com/guides/checkout).
If you have questions or issues feel free to reach out via our [Discord server](https://discord.gg/xpCwq3Kfn8) for direct access to the Medusa engineering team.
+107
View File
@@ -0,0 +1,107 @@
---
title: Checkouts
---
# Checkouts
## Introduction
The purpose of this guide is to describe a checkout flow in Medusa. It is assumed that you've completed our [Quickstart](https://docs.medusa-commerce.com/quickstart/quick-start) or [Tutorial](https://docs.medusa-commerce.com/tutorial/set-up-your-development-environment) and are familiar with the technologies we use in our stack. Additionally, having an understanding of the [core API](https://docs.medusa-commerce.com/api/store/auth) would serve as a great foundation for this walkthrough.
> All code snippets in the following guide, use the JS SDK distributed through **npm**. To install it, run `yarn add @medusajs/medusa-js` or `npm install @medusajs/medusa-js`.
## Glossary
- **Cart**: The Cart contains all the information needed for customers to complete an Order. In the Cart customers gather the items they wish to purchase, they add shipping and billing details and complete payment information.
- **LineItem**: Line Items represent an expense added to a Cart. Typically this will be a Product Variant and a certain quantity of the same variant. Line Items hold descriptive fields that help communicate its contents and price.
- **PaymentSession**: A Payment Session is a Medusa abstraction that unifies the APIs of multiple payment gateways. Payment Sessions are _initialized_ when the customer begins a checkout and are _authorized_ prior to an Order being placed. Payment Sessions are created based on the available Payment Providers configured in a Cart's region.
- **ShippingOption**: A Shipping Option represents a way in which an Order can be fulfilled. Shipping Options have a price and are associated with a Fulfillment Provider that will handle the shipment later in the Order flow. Once a customer selects a Shipping Option it becomes a Shipping Method.
- **ShippingMethod**: Shipping Methods are unique to each Cart and can thereby hold either overwrites for fields in a Shipping Option (e.g. price) or additional details (e.g. an id representing a parcel pickup location).
## Checkout flow
To create an order from a cart, we go through the following flow.
> At this point, it assumed that the customer has created a cart, added items and is now at the initial step of the checkout flow.
#### Initializing the checkout
The first step in the flow is to _initialize_ the configured Payment Sessions for the Cart. If you are using the `medusa-starter-default` starter, this call will result in the `cart.payment_sessions` array being filled with one Payment Session for the manual payment provider.
```javascript=
const { cart } = await medusa.carts.createPaymentSessions("cart_id")
```
To give a more real life example, it is assumed that `medusa-payment-stripe` is installed in your project in which case the call will result in a [Stripe PaymentIntent](https://stripe.com/docs/api/payment_intents) being created. The unique provider data for each Payment Session can be found in the Payment Session's `data` field; this data can be used in front end implementations e.g. if using Stripe Elements the `client_secret` can be retrieved through `session.data.client_secret`.
#### Adding customer information
After initializing the checkout flow, you would typically have one big step or several smaller steps for gathering user information; email, address, country, and more. To store this data you may update the cart with each of field or all fields at the same time.
```javascript=
const { cart } = await medusa.carts.update("cart_id", {
email: "lebron@james.com",
shipping_address: {
first_name: "",
last_name: "",
...
}
})
```
#### Selecting payment provider
This step is only applicable, if you have multiple Payment Sessions installed in your project. In cases where only one Payment Provider is configured the Payment Session will be preselected. In all other cases your implementations should call:
```javascript=
const { cart } = await medusa.carts.setPaymentSession("cart_id", {
provider_id: "stripe"
})
```
#### Choosing a shipping method
Before reaching the payment step, you would typically require the customer to choose a Shipping Method from a number of options. In Medusa you can set rules that must be met for a Shipping Option to be available for a Cart. To get the available shipping options for a Cart you should call:
```javascript=
const { shipping_options } = await medusa.carts.listCartOptions("cart_id")
```
Choosing a Shipping Option, will create a Shipping Method and attach it to the Cart. The second argument to the function in the snippet below holds the id of the selected option.
```javascript=
const { cart } = await medusa.carts.addShippingMethod("cart_id", { option_id: "option_id"})
```
#### Collecting payment details
The following snippet shows, how we use Stripe to collect payment details from the customer. Note that we are using the `client_secret` from the Stripe PaymentIntent in `data` on the payment session as this is required by Stripe Elements.
```javascript=
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
...
const stripe = useStripe();
const elements = useElements();
const handleSubmit = () => {
...
const { paymentIntent, error } = await stripe.confirmCardPayment(
cart.payment_session.data.client_secret,
{
payment_method: {
card: elements.getElement(CardElement),
},
}
);
...
}
return <CardElement id="card-element" />
```
After collecting the payment details, the customer can complete the checkout flow.
#### Completing the cart
When all relevant customer information has been captured, your implementation should proceed to the final step; completing the cart.
```javascript=
const { order } = await medusa.carts.complete("cart_id")
```
If all information is collected correctly throughout the checkout flow, the call will place an Order based on the details gathered in the Cart.
## Summary
You now have a solid foundation for creating your own checkout flows using Medusa. Throughout this guide, we've used Stripe as a Payment Provider. Stripe is one of the most popular providers and we have an official plugin, that you can easily install in your project.
## What's next?
See the checkout flow, explained in the previous sections, in one of our frontend starters:
- [Nextjs Starter](https://github.com/medusajs/nextjs-starter-medusa)
- [Gatsby Starter](https://github.com/medusajs/gatsby-starter-medusa)
+71
View File
@@ -0,0 +1,71 @@
---
title: Fulfillment API
---
## **Introduction**
This guide will give an overview of Medusa's Fulfillment API and how it can be implemented to work with different fulfillment providers. Before digging deeper into the API it should be clarified what is meant by a fulfillment provider; in Medusa a fulfillment provider is typically a 3rd party service that can handle order data for the purpose of shipping the items in the order to a customer. Examples of fulfillment providers are: a carrier like UPS, a logistics platform like ShipBob or a 3PL solution.
Implementations of the Fulfillment API can be distributed as npm packages for easy installation through the plugin system, there are already a couple of examples of fulfillment plugins in the Medusa monorepo, you can identify these by looking for `medusa-fulfillment-*`. For further details on building and publishing plugins [please check this guide](https://docs.medusa-commerce.com/how-to/plugins).
## Fulfillment Service Interface
The fulfillment service interface can be found in the [`medusa-interfaces` package](https://github.com/medusajs/medusa/blob/master/packages/medusa-interfaces/src/fulfillment-service.js) where you can see the full list of methods that can be implemented. The methods in the interface allow you to implement plugins that can calculate shipping rates, create fulfillments in other systems, create return labels, retrieve customs documents and more.
In this guide we will focus on the methods involved in a typical fulfillment flow i.e. creating a shipping option, adding a shipping method to a cart, creating a fulfillment of items in a cart and finally marking the fulfillment as shipped. The methods involved in this flow are:
```jsx
getFulfillmentOptions()
validateOption(optionData)
validateFulfillmentData(fulfillmentData, cart)
createFulfillment(fulfillmentData, fulfillmentItems, order, fulfillment)
```
The figure below shows at what points in the flow the Fulfillment API is called.
![Fulfillment Flow](https://user-images.githubusercontent.com/7554214/133107092-981505ea-230a-4399-9d95-3f2ec59a7dcc.png)
- **Get fulfillment options for Region**
In Medusa Shipping Options are defined and configured for each region - this allows store operators to granularly control how orders can be fulfilled in different countries. When creating a shipping option on a Region the Fulfillment API calls `getFulfillmentOptions` which will return the ways in which a fulfillment provider can process an order. Let's imagine that you have built a UPS plugin that can be used to fulfill orders with UPS; in this case `getFulfillmentOptions` might respond with UPS Express Shipping and UPS Access Point.
- **Create Shipping Option**
When creating the Shipping Option in Medusa, a store operator selects which underlying fulfillment option will be used when customers create Orders with the Shipping Option. To build from the UPS example a store operator may select the UPS Access Point option from the list of fulfillment options, she will then add an appropriate name for the Shipping Option, set the Shipping Option's price and if needed adjust the requirements that must be met for the Shipping Option to be active (e.g. minimum cart subtotal). Before the Shipping Option is created, `validateOption` will be called to ensure that the selected fulfillment option is in fact valid and correctly formatted. `validateOption` should respond with the validated fulfillment option; this also provides a point at which you may add additional details to the fulfillment data before the Shipping Option is created.
- **Get Shipping Options for Cart**
Moving to the customer perspective it is assumed that a cart has been created, items have been added to the cart and the customer is now looking towards selecting the Shipping Option they wish to have their Order fulfilled with. The first step from the storefront perspective is to retrieve the list of Shipping Options that are available for the Cart; these will be filtered based on the Region the Cart belongs to and the items that are in the cart. There are no calls to the Fulfillment API associated with the retrieval.
- **Add Shipping Method to Cart**
At this point the customer has selected a Shipping Option and may have configured additional details about the fulfillment. For example, building on the UPS example, the customer may have selected a Shipping Option that uses the UPS Access Point fulfillment option; in this case the customer sends an `access_point_id` in the `data` object of the `POST /store/carts/:id/shipping-methods` payload. At this point the Shipping Option becomes a Shipping Method, the distinction between the two is that a Shipping Option is a template for how a cart may be shipped whereas a Shipping Method is the instantiated way that the cart will be shipped (which may include specific details like the `access_point_id`). In order to ensure that the details that the customer selects are valid the Fulfillment API's `validateFulfillmentData` is called, this is where a fulfillment implementation may check that the `access_point_id` is in fact a valid value, etc.
- **Create Fulfillment**
It is now assumed that the customer has completed their purchase with a given Shipping Method and the order has been confirmed. At this point we are ready to create the fulfillment in the 3rd party system. When a store operator (or an automation) attempts to fulfill an order the `createFulfillment` method will be called in the Fulfillment API, in our UPS example this method should then book a shipment via UPS's API.
Note: in Medusa you can make partial fulfillments of an order it is therefore important that the id sent to the 3rd party is unique by fulfillment and not only order.
- **Mark as shipped**
The final step of the fulfillment process is to mark the fulfillment as shipped. It is also at this stage that you would record a tracking number that can be shared with the customer. This step typically happens asynchronously through a webhook.
## Other useful methods
The flow above describes the Fulfillment API in high level terms; check out the `[medusa-fulfillment-webshipper](https://github.com/medusajs/medusa/blob/master/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js)` plugin for a full implementation of the Fulfillment API. In the implementation you will find examples of all the method described above.
### `createReturn`
You will find that the Webshipper plugin has an implementation for `createReturn` which allows fulfillment providers to implement a way for generating return labels. When implemented this can be used to allow customers to create self managed returns or to integrate automatic return label generation in the admin dashboard.
### `canCalculate(fulfillmentOption)`
If implemented this method can be used to dynamically calcluate prices based on a cart's contents or details. The method returns a boolean value indicating if a given fulfillment option can have a dynamically calculated price or not.
### `calculatePrice(fulfillmentOption, fulfillmentData, cart)`
If the shipping option is configured to dynamically calculate the price of the this method will be called when Shipping Options are fetched for the Cart and when Shipping Methods are created on a Cart.
## What's next?
Now that you have an overview of the Fulfillment API you can start developing your own fulfillment plugin. For a guide on how to create plugins [check this guide](https://docs.medusa-commerce.com/how-to/plugins). If you have questions or issues please feel free to [join our Discord server](https://discord.gg/H6naACAK) for direct access to the engineering team.
+117
View File
@@ -0,0 +1,117 @@
---
title: Using create-medusa-app
---
# Using create-medusa-app
With the new `create-medusa-app` tool you will get your [Medusa](https://github.com/medusajs/medusa) development environment ready within a couple of minutes. After completion, you will have a Medusa backend, a Gatsby or Next.js storefront, and an admin dashboard up and running on your local machine.
Starting a new e-commerce project just got easier, now with one command.
## Getting started with `create-medusa-app`
Use `create-medusa-app` with your preferred package manager:
```bash
yarn create medusa-app
npx create-medusa-app
```
Behind the scenes, `create-medusa-app` is populating your database with some initial set of mock data, which helps to interact with Medusa setup intuitively straight away.
Right after hitting one of those commands, the multistep installation process will be initiated, so the starter can be shaped right for the specific needs.
### Destination folder
Enter the path to the directory that will become the root of your Medusa project:
```bash
? Where should your project be installed? › my-medusa-store
```
### Pick the starter you prefer
```bash
? Which Medusa starter would you like to install? …
❯ medusa-starter-default
medusa-starter-contentful
Other
```
You will be presented with three options:
- `medusa-starter-default` is the most lightweight version of a Medusa project
- `medusa-starter-contentful` almost like the default starter, but with `medusa-plugin-contentful` preinstalled
- `Other` if you have a different starter that you would wish to install from `Other` will give you the option of providing a URL to that starter. An additional question will be asked if you choose this option:
```bash
Where is the starter located? (URL or path) › https://github.com/somecoolusername/my-custom-medusa-starter
```
For the walkthrough purposes, we assume that the selected starter is `medusa-starter-default` and proceed to the next step.
### Selecting a Storefront
After selecting your Medusa starter you will be given the option to install one of our storefront starters. At the moment we have starters for Gatsby and Next.js:
```bash
Which storefront starter would you like to install? …
❯ Gatsby Starter
Next.js Starter
None
```
You may also select `None` if the choice is to craft a custom storefront for your product.
`create-medusa-app` now has all of the info necessary for the installation to begin.
```bash
Creating new project from git: https://github.com/medusajs/medusa-starter-default.git
✔ Created starter directory layout
Installing packages...
```
Once the installation has been completed you will have a Medusa backend, a demo storefront, and an admin dashboard.
## What's inside
Inside the root folder which was specified at the beginning of the installation process the following structure could be found:
```bash
/my-medusa-store
/storefront // Medusa storefront starter
/backend // Medusa starter as a backend option
/admin // Medusa admin panel
```
`create-medusa-app` prints out the commands that are available to you after installation. When each project is started you can visit your storefront, complete the order, and view the order in Medusa admin.
```bash
⠴ Installing packages...
✔ Packages installed
Initialising git in my-medusa-store/admin
Create initial git commit in my-medusa-store/admin
Your project is ready 🚀. The available commands are:
Medusa API
cd my-medusa-store/backend
yarn start
Admin
cd my-medusa-store/admin
yarn start
Storefront
cd my-medusa-store/storefront
yarn start
```
## **What's next?**
To learn more about Medusa to go through our docs to get some inspiration and guidance for the next steps and further development:
- [Find out how to set up a Medusa project with Gatsby and Contentful](https://docs.medusa-commerce.com/how-to/headless-ecommerce-store-with-gatsby-contentful-medusa)
- [Move your Medusa setup to the next level with some custom functionality](https://docs.medusa-commerce.com/tutorial/adding-custom-functionality)
- [Create your own Medusa plugin](https://docs.medusa-commerce.com/how-to/plugins)
If you have any follow-up questions or want to chat directly with our engineering team we are always happy to meet you at our [Discord](https://discord.gg/DSHySyMu).
+1 -1
View File
@@ -10,7 +10,7 @@ The purpose of this guide is to give an introduction to the structure of a plugi
Plugins offer a way to extend and integrate the core functionality of Medusa.
In most commerce solutions, you can extend the basic features but it often comes with the expense of having to build standalone web applications. Our architecture is built such that plugins run within the same process as the core eliminating the need for extra server capacaity, infrastructure and maintenance. As a result, the plugins can use all other services as dependencies and access the database.
In most commerce solutions, you can extend the basic features but it often comes with the expense of having to build standalone web applications. Our architecture is built such that plugins run within the same process as the core eliminating the need for extra server capacity, infrastructure and maintenance. As a result, the plugins can use all other services as dependencies and access the database.
> You will notice that plugins vary in naming. The name should signal what functionality they provide.
@@ -0,0 +1,54 @@
---
title: Setting up a Next.js storefront for your Medusa project
---
# Setting up a Next.js storefront for your Medusa project
> Medusa is a headless open source commerce platform giving engineers the foundation for building unique and scaleable digital commerce projects through our API-first engine.
> Being headless, our starters serve as a good foundation for you to get coupled with a frontend in a matter of minutes.
This article assumes you already have the Medusa project created and ready to be linked to your Next.js starter.
## Getting started
In order to get started let's open the terminal and use the following command to create an instance of your storefront:
```zsh
npx create-next-app -e https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
```
Now we have a storefront codebase that is ready to be used with our Medusa server.
Next, we have to complete two steps to make our new shiny storefront to speak with our server: **link storefront to a server** and **update the `STORE_CORS` variable**.
Let's jump to these two.
## Link storefront to a server
For this part, we should navigate to a `client.js` file which you can find in the utils folder.
We don't need to do much in here, but to make sure that our storefront is pointing to the port, where the server is running
```js
import Medusa from "@medusajs/medusa-js"
const BACKEND_URL = process.env.GATSBY_STORE_URL || "http://localhost:9000" // <--- That is the line we are looking for
export const createClient = () => new Medusa({ baseUrl: BACKEND_URL })
```
By default the Medusa server is running at port 9000, so if you didn't change that we are good to go to our next step.
## Update the `STORE_CORS` variable
Here let's navigate to your Medusa server and open `medusa-config.js`
Let's locate the `STORE_CORS` variable and make sure it's the right port (which is 3000 by default for Next.js projects)
```js
/*
* CORS to avoid issues when consuming Medusa from a client.
* Should be pointing to the port where the storefront is running.
*/
const STORE_CORS = process.env.STORE_CORS || "http://localhost:3000"
```
Now we have a storefront that interacts with our Medusa server and with that we have a sweet and complete e-commerce setup with a Next.js storefront.
+2 -2
View File
@@ -32,6 +32,6 @@ We have created two starters for you that can help you lay a foundation for your
- [Nextjs Starter](https://github.com/medusajs/nextjs-starter-medusa)
- [Gatsby Starter](https://github.com/medusajs/gatsby-starter-medusa)
### Link you local development to Medusa Cloud (Coming soon!)
<!-- ### Link you local development to Medusa Cloud (Coming soon!)
With your project in local development you can link your Medusa instance to Medusa Cloud - this will allow you to manage your store, view orders and test out the amazing functionalities that you are building. [Get started here](https://docs.medusa-commerce.com/tutorial/linking-your-local-project-with-medusa-cloud).
With your project in local development you can link your Medusa instance to Medusa Cloud - this will allow you to manage your store, view orders and test out the amazing functionalities that you are building. [Get started here](https://docs.medusa-commerce.com/tutorial/linking-your-local-project-with-medusa-cloud). -->
@@ -10,7 +10,7 @@ Welcome to Medusa - we are so excited to get you on board!
This tutorial will walk you through the steps to take to set up your local development environment. You will familiarize yourself with some of the core parts that make Medusa work and learn how to configure your development environment. Furthermore you will be introduced to how the plugin architecture works and how to customize your commerce functionalities with custom logic and endpoints.
As a final part of the tutorial you will be linking your local project to Medusa Cloud where you can leverage advanced tools that make it easy to develop, test and deploy your Medusa project.
<!-- As a final part of the tutorial you will be linking your local project to Medusa Cloud where you can leverage advanced tools that make it easy to develop, test and deploy your Medusa project. -->
## Background Knowledge and Prerequisites
@@ -107,11 +107,11 @@ If you don't already have a text editor of choice you should find one you like -
It is not important which editor you use as long as you feel comfortable working with it.
## Medusa Cloud account
<!-- ## Medusa Cloud account
As the final step in this part of the tutorial you should create a Medusa Cloud account. Medusa Cloud is the platform that works with Medusa; the platform is where you view and manage your store, but is also a key part of the development process as you will be linking your local project to the platform so that you can manage your store while in development.
[Sign up for Medusa Cloud](https://app.medusa-commerce.com)
[Sign up for Medusa Cloud](https://app.medusa-commerce.com) -->
## Summary
@@ -6,7 +6,7 @@ title: 2. Adding custom functionality
## Introduction
In the previous part of the tutorial we set up your Medusa project using the `medusa new` and started your Medusa server locally. In this part we will start adding some custom functionality that extends the core. In particular this tutorial will take you through adding custom serverices, custom endpoints and subscribers. The custom functionality that we will be adding will create an endpoint called `/welcome/:cart_id` which customers of your store can use to opt-in to receiving a welcome in their email inbox after completing their order.
In the previous part of the tutorial we set up your Medusa project using the `medusa new` and started your Medusa server locally. In this part we will start adding some custom functionality that extends the core. In particular this tutorial will take you through adding custom services, custom endpoints and subscribers. The custom functionality that we will be adding will create an endpoint called `/welcome/:cart_id` which customers of your store can use to opt-in to receiving a welcome in their email inbox after completing their order.
The custom functionality will do a number of things:
@@ -56,7 +56,7 @@ In the constructor we specify that our `WelcomeService` will depend upon the `ca
### `registerOptin`
The `registerOption` function will take to arguments: `cartId` and `optin`, where `cartId` holds the id of the cart that we wish to register optin for and `optin` is a boolean to indicate if the customer has accepted or optin or not. We will save the `optin` preferences in the cart's `metadata` field, so that it can be persisted for the future when we need to evaluate if we should send the welcome or not.
The `registerOption` function will take two arguments: `cartId` and `optin`, where `cartId` holds the id of the cart that we wish to register optin for and `optin` is a boolean to indicate if the customer has accepted or optin or not. We will save the `optin` preferences in the cart's `metadata` field, so that it can be persisted for the future when we need to evaluate if we should send the welcome or not.
```javascript
async registerOptin(cartId, optin) {
@@ -111,7 +111,7 @@ In the above implementation we are first retrieving the order that we need to ch
After retrieving the order we list all orders that have the same `customer_id` as the order we just retrieved. We are only interested in the count of these orders so it is sufficient for us to just select the ids of these orders.
We then check if the number of previous orders is 0, indicating that the customer has not previously purchased anything from our store. If the number of previous orders is greater than 0 we can exit our function prematurely as we only send welcomes to new customers.
We then check if the number of previous orders is greater than 1, indicating that the customer has previously purchased anything from our store. If the number of previous orders is greater than 1 we can exit our function prematurely as we only send welcomes to new customers.
The final part of the implementation checks if the `welcome_optin` metadata has been set to true. If the customer has opted in we use `someEmailService.send` to trigger and email dispatch to the email stored on the order. In this case `someEmailSender` could be any email service for example Sendgrid, SES, Mailgun, etc.
@@ -252,4 +252,4 @@ You have now learned how to add custom functionality to your Medusa server, whic
You have now been introduced to many of the key parts of Medusa and with your knowledge of customization you can now begin creating some really powerful commerce experiences. If you have an idea for a cool customization go ahead and make it right now! If you are not completely ready yet you can browse the reference docs further.
In the next part of this tutorial we will look into linking your local project with Medusa Cloud to make develpment smoother while leveraging the powerful management tools that merchants use to manage their Medusa store.
<!-- In the next part of this tutorial we will look into linking your local project with Medusa Cloud to make develpment smoother while leveraging the powerful management tools that merchants use to manage their Medusa store. -->
@@ -0,0 +1,384 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/products GET /admin/products returns a list of products with child entities 1`] = `
Array [
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"title": "Test collection",
"updated_at": Any<String>,
},
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product",
"height": null,
"hs_code": null,
"id": StringMatching /\\^test-\\*/,
"images": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"updated_at": Any<String>,
"url": "test-image.png",
},
],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"product_id": StringMatching /\\^test-\\*/,
"title": "test-option",
"updated_at": Any<String>,
},
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^tag\\*/,
"metadata": null,
"updated_at": Any<String>,
"value": "123",
},
],
"thumbnail": null,
"title": "Test product",
"type": Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"updated_at": Any<String>,
"value": "test-type",
},
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": "test-barcode",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean",
"height": null,
"hs_code": null,
"id": "test-variant",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^test-price\\*/,
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"allow_backorder": false,
"barcode": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean2",
"height": null,
"hs_code": null,
"id": "test-variant_2",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant 2",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^test-price\\*/,
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku2",
"title": "Test variant rank (2)",
"upc": "test-upc2",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"allow_backorder": false,
"barcode": "test-barcode 1",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean1",
"height": null,
"hs_code": null,
"id": "test-variant_1",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant 1",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^test-price\\*/,
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku1",
"title": "Test variant rank (1)",
"upc": "test-upc1",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
},
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"title": "Test collection",
"updated_at": Any<String>,
},
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description1",
"discountable": true,
"handle": "test-product1",
"height": null,
"hs_code": null,
"id": StringMatching /\\^test-\\*/,
"images": Array [],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^tag\\*/,
"metadata": null,
"updated_at": Any<String>,
"value": "123",
},
],
"thumbnail": null,
"title": "Test product1",
"type": Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-\\*/,
"metadata": null,
"updated_at": Any<String>,
"value": "test-type",
},
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean4",
"height": null,
"hs_code": null,
"id": "test-variant_4",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant 4",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^test-price\\*/,
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku4",
"title": "Test variant rank (2)",
"upc": "test-upc4",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"allow_backorder": false,
"barcode": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean3",
"height": null,
"hs_code": null,
"id": "test-variant_3",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant 3",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^test-price\\*/,
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku3",
"title": "Test variant rank (2)",
"upc": "test-upc3",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
},
]
`;
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/return-reasons POST /admin/return-reasons creates a return_reason 1`] = `
Object {
"created_at": Any<String>,
"deleted_at": null,
"description": "Use this if the size was too big",
"id": Any<String>,
"label": "Too Big",
"parent_return_reason": null,
"parent_return_reason_id": null,
"return_reason_children": Array [],
"updated_at": Any<String>,
"value": "too_big",
}
`;
+100 -50
View File
@@ -1,50 +1,50 @@
const { dropDatabase } = require("pg-god");
const path = require("path");
const { dropDatabase } = require("pg-god")
const path = require("path")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { useDb, initDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { useDb, initDb } = require("../../../helpers/use-db")
const customerSeeder = require("../../helpers/customer-seeder");
const adminSeeder = require("../../helpers/admin-seeder");
const customerSeeder = require("../../helpers/customer-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/customers", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
const db = useDb()
await db.shutdown()
medusaProcess.kill();
});
medusaProcess.kill()
})
describe("GET /admin/customers", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await customerSeeder(dbConnection);
await adminSeeder(dbConnection)
await customerSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("lists customers and query count", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/customers", {
@@ -53,11 +53,11 @@ describe("/admin/customers", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.count).toEqual(3);
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(4)
expect(response.data.customers).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -69,25 +69,28 @@ describe("/admin/customers", () => {
expect.objectContaining({
id: "test-customer-3",
}),
expect.objectContaining({
id: "test-customer-has_account",
}),
])
);
});
)
})
it("lists customers with specific query", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/customers?q=test2@email.com", {
.get("/admin/customers?q=est2@", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.count).toEqual(1);
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.customers).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -95,11 +98,11 @@ describe("/admin/customers", () => {
email: "test2@email.com",
}),
])
);
});
)
})
it("lists customers with expand query", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/customers?q=test1@email.com&expand=shipping_addresses", {
@@ -108,11 +111,11 @@ describe("/admin/customers", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.count).toEqual(1);
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.customers).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -126,7 +129,54 @@ describe("/admin/customers", () => {
]),
}),
])
);
});
});
});
)
})
})
describe("POST /admin/customers/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await customerSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("Correctly updates customer", async () => {
const api = useApi()
const response = await api
.post(
"/admin/customers/test-customer-3",
{
first_name: "newf",
last_name: "newl",
email: "new@email.com",
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.customer).toEqual(
expect.objectContaining({
first_name: "newf",
last_name: "newl",
email: "new@email.com",
})
)
})
})
})
+162 -71
View File
@@ -1,46 +1,107 @@
const path = require("path");
const { Region, DiscountRule, Discount } = require("@medusajs/medusa");
const path = require("path")
const { Region, DiscountRule, Discount } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const adminSeeder = require("../../helpers/admin-seeder");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/discounts", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
medusaProcess.kill();
});
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/discounts", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
await manager.insert(Discount, {
id: "messi-discount",
code: "BARCA100",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
} catch (err) {
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should list discounts that match a specific query in a case insensitive manner", async () => {
const api = useApi()
const response = await api
.get("/admin/discounts?q=barca", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.discounts).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "messi-discount",
code: "BARCA100",
}),
])
)
})
})
describe("POST /admin/discounts", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await adminSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a discount and updates it", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -62,16 +123,16 @@ describe("/admin/discounts", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 10,
})
);
)
const updated = await api
.post(
@@ -86,51 +147,51 @@ describe("/admin/discounts", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(updated.status).toEqual(200);
expect(updated.status).toEqual(200)
expect(updated.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 20,
})
);
});
});
)
})
})
describe("testing for soft-deletion + uniqueness on discount codes", () => {
let manager;
let manager
beforeEach(async () => {
manager = dbConnection.manager;
manager = dbConnection.manager
try {
await adminSeeder(dbConnection);
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
});
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
});
})
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("successfully creates discount with soft-deleted discount code", async () => {
const api = useApi();
const api = useApi()
// First we soft-delete the discount
await api
@@ -140,8 +201,8 @@ describe("/admin/discounts", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
// Lets try to create a discount with same code as deleted one
const response = await api
@@ -164,51 +225,81 @@ describe("/admin/discounts", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "TESTING",
usage_limit: 10,
})
);
});
});
)
})
it("should fails when creating a discount with already existing code", async () => {
const api = useApi()
// Lets try to create a discount with same code as deleted one
try {
await api.post(
"/admin/discounts",
{
code: "TESTING",
rule: {
description: "test",
type: "percentage",
value: 10,
allocation: "total",
},
usage_limit: 10,
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
} catch (error) {
expect(error.response.data.message).toMatch(
/duplicate key value violates unique constraint/i
)
}
})
})
describe("POST /admin/discounts/:discount_id/dynamic-codes", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection);
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Dynamic rule",
type: "percentage",
value: 10,
allocation: "total",
});
})
await manager.insert(Discount, {
id: "test-discount",
code: "DYNAMIC",
is_dynamic: true,
is_disabled: false,
rule_id: "test-discount-rule",
});
})
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a dynamic discount", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -223,10 +314,10 @@ describe("/admin/discounts", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
});
});
});
expect(response.status).toEqual(200)
})
})
})
@@ -1,49 +1,49 @@
const path = require("path");
const path = require("path")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const draftOrderSeeder = require("../../helpers/draft-order-seeder");
const adminSeeder = require("../../helpers/admin-seeder");
const draftOrderSeeder = require("../../helpers/draft-order-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/draft-orders", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
const db = useDb()
await db.shutdown()
medusaProcess.kill();
});
medusaProcess.kill()
})
describe("POST /admin/draft-orders", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await draftOrderSeeder(dbConnection);
await adminSeeder(dbConnection)
await draftOrderSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a draft order cart", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -62,7 +62,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -71,13 +71,53 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
console.log(err)
})
expect(response.status).toEqual(200)
})
it("creates a draft order cart and creates new user", async () => {
const api = useApi()
const payload = {
email: "non-existing@test.dk",
customer_id: "non-existing",
shipping_address: "oli-shipping",
items: [
{
variant_id: "test-variant",
quantity: 2,
metadata: {},
},
],
region_id: "test-region",
shipping_methods: [
{
option_id: "test-option",
},
],
}
const response = await api
.post("/admin/draft-orders", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
const draftOrder = response.data.draft_order
expect(draftOrder.cart.customer_id).toBeDefined()
expect(draftOrder.cart.email).toEqual("non-existing@test.dk")
})
it("fails to create a draft order with option requirement", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -96,7 +136,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option-req",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -105,13 +145,13 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
return err.response;
});
expect(response.status).toEqual(400);
});
return err.response
})
expect(response.status).toEqual(400)
})
it("creates a draft order with option requirement", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -135,7 +175,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option-req",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -144,13 +184,13 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
console.log(err)
})
expect(response.status).toEqual(200)
})
it("creates a draft order with custom item", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -174,7 +214,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -183,13 +223,13 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
console.log(err)
})
expect(response.status).toEqual(200)
})
it("creates a draft order with product variant with custom price and custom item price set to 0", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -215,7 +255,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -224,8 +264,8 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
const created = await api
.get(`/admin/draft-orders/${response.data.draft_order.id}`, {
@@ -234,10 +274,10 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(created.data.draft_order.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -248,17 +288,17 @@ describe("/admin/draft-orders", () => {
unit_price: 0,
}),
])
);
)
// Check that discount is applied
expect(created.data.draft_order.cart.discounts[0]).toEqual(
expect.objectContaining({
code: "TEST",
})
);
});
)
})
it("creates a draft order with created shipping address", async () => {
const api = useApi();
const api = useApi()
const payload = {
email: "oli@test.dk",
@@ -289,7 +329,7 @@ describe("/admin/draft-orders", () => {
option_id: "test-option",
},
],
};
}
const response = await api
.post("/admin/draft-orders", payload, {
@@ -298,13 +338,13 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
console.log(err)
})
expect(response.status).toEqual(200)
})
it("creates a draft order and registers manual payment", async () => {
const api = useApi();
const api = useApi()
// register system payment for draft order
const orderResponse = await api.post(
@@ -315,7 +355,7 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
const createdOrder = await api.get(
`/admin/orders/${orderResponse.data.order.id}`,
@@ -324,7 +364,7 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`,
@@ -333,40 +373,38 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
expect(orderResponse.status).toEqual(200);
expect(orderResponse.status).toEqual(200)
// expect newly created order to have id of draft order and system payment
expect(createdOrder.data.order.draft_order_id).toEqual(
"test-draft-order"
);
expect(createdOrder.data.order.draft_order_id).toEqual("test-draft-order")
expect(createdOrder.data.order.payments).toEqual(
expect.arrayContaining([
expect.objectContaining({ provider_id: "system" }),
])
);
)
// expect draft order to be complete
expect(updatedDraftOrder.data.draft_order.status).toEqual("completed");
expect(updatedDraftOrder.data.draft_order.completed_at).not.toEqual(null);
});
});
expect(updatedDraftOrder.data.draft_order.status).toEqual("completed")
expect(updatedDraftOrder.data.draft_order.completed_at).not.toEqual(null)
})
})
describe("GET /admin/draft-orders", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await draftOrderSeeder(dbConnection);
await adminSeeder(dbConnection)
await draftOrderSeeder(dbConnection)
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("lists draft orders", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/draft-orders", {
@@ -375,20 +413,20 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.draft_orders).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: "test-draft-order" }),
])
);
});
)
})
it("lists draft orders with query", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/draft-orders?q=oli@test", {
@@ -397,10 +435,10 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.draft_orders).toEqual(
expect.arrayContaining([
@@ -408,11 +446,11 @@ describe("/admin/draft-orders", () => {
cart: expect.objectContaining({ email: "oli@test.dk" }),
}),
])
);
});
)
})
it("lists no draft orders on query for non-existing email", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/draft-orders?q=heyo@heyo.dk", {
@@ -421,34 +459,34 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.draft_orders).toEqual([]);
expect(response.data.count).toEqual(0);
});
});
expect(response.data.draft_orders).toEqual([])
expect(response.data.count).toEqual(0)
})
})
describe("DELETE /admin/draft-orders/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await draftOrderSeeder(dbConnection);
await adminSeeder(dbConnection)
await draftOrderSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("deletes a draft order", async () => {
const api = useApi();
const api = useApi()
const response = await api
.delete("/admin/draft-orders/test-draft-order", {
@@ -457,36 +495,36 @@ describe("/admin/draft-orders", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: "test-draft-order",
object: "draft-order",
deleted: true,
});
});
});
})
})
})
describe("POST /admin/draft-orders/:id/line-items/:line_id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await draftOrderSeeder(dbConnection, { status: "open" });
await adminSeeder(dbConnection)
await draftOrderSeeder(dbConnection, { status: "open" })
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("updates a line item on the draft order", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -502,10 +540,10 @@ describe("/admin/draft-orders", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`,
@@ -514,16 +552,16 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
const item = updatedDraftOrder.data.draft_order.cart.items[0];
const item = updatedDraftOrder.data.draft_order.cart.items[0]
expect(item.title).toEqual("Update title");
expect(item.unit_price).toEqual(1000);
});
expect(item.title).toEqual("Update title")
expect(item.unit_price).toEqual(1000)
})
it("removes the line item, if quantity is 0", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -539,10 +577,10 @@ describe("/admin/draft-orders", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`,
@@ -551,31 +589,31 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
const items = updatedDraftOrder.data.draft_order.cart.items;
const items = updatedDraftOrder.data.draft_order.cart.items
expect(items).toEqual([]);
});
});
expect(items).toEqual([])
})
})
describe("POST /admin/draft-orders/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await draftOrderSeeder(dbConnection, { status: "open" });
await adminSeeder(dbConnection)
await draftOrderSeeder(dbConnection, { status: "open" })
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("updates a line item on the draft order", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -607,10 +645,10 @@ describe("/admin/draft-orders", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`,
@@ -619,14 +657,14 @@ describe("/admin/draft-orders", () => {
Authorization: "Bearer test_token",
},
}
);
)
const dorder = updatedDraftOrder.data.draft_order;
const dorder = updatedDraftOrder.data.draft_order
expect(dorder.cart.email).toEqual("lebron@james.com");
expect(dorder.cart.billing_address.first_name).toEqual("lebron");
expect(dorder.cart.shipping_address.last_name).toEqual("james");
expect(dorder.cart.discounts[0].code).toEqual("TEST");
});
});
});
expect(dorder.cart.email).toEqual("lebron@james.com")
expect(dorder.cart.billing_address.first_name).toEqual("lebron")
expect(dorder.cart.shipping_address.last_name).toEqual("james")
expect(dorder.cart.discounts[0].code).toEqual("TEST")
})
})
})
@@ -1,54 +1,162 @@
const path = require("path");
const { Region } = require("@medusajs/medusa");
const path = require("path")
const { Region, GiftCard } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const adminSeeder = require("../../helpers/admin-seeder");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/gift-cards", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
const db = useDb()
await db.shutdown()
medusaProcess.kill();
});
medusaProcess.kill()
})
describe("GET /admin/gift-cards", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
await manager.insert(GiftCard, {
id: "gift_test",
code: "GC_TEST",
value: 20000,
balance: 20000,
region_id: "test-region",
})
await manager.insert(GiftCard, {
id: "another_gift_test",
code: "CARD_TEST",
value: 200000,
balance: 200000,
region_id: "test-region",
})
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("lists gift cards and query count", async () => {
const api = useApi()
const response = await api
.get("/admin/gift-cards", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.gift_cards).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "gift_test",
code: "GC_TEST",
}),
expect.objectContaining({
id: "another_gift_test",
code: "CARD_TEST",
}),
])
)
})
it("lists gift cards with specific query", async () => {
const api = useApi()
const response = await api
.get("/admin/gift-cards?q=gc", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.gift_cards.length).toEqual(1)
expect(response.data.gift_cards).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "gift_test",
code: "GC_TEST",
}),
])
)
})
it("lists no gift cards on query for non-existing gift card code", async () => {
const api = useApi()
const response = await api
.get("/admin/gift-cards?q=bla", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.gift_cards.length).toEqual(0)
expect(response.data.gift_cards).toEqual([])
})
})
describe("POST /admin/gift-cards", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection);
await adminSeeder(dbConnection)
await manager.insert(Region, {
id: "region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
})
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a gift card", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post(
@@ -64,13 +172,13 @@ describe("/admin/gift-cards", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.gift_card.value).toEqual(1000);
expect(response.data.gift_card.balance).toEqual(1000);
expect(response.data.gift_card.region_id).toEqual("region");
});
});
});
expect(response.status).toEqual(200)
expect(response.data.gift_card.value).toEqual(1000)
expect(response.data.gift_card.balance).toEqual(1000)
expect(response.data.gift_card.region_id).toEqual("region")
})
})
})
@@ -0,0 +1,268 @@
const path = require("path")
const { Note } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000)
const note = {
id: "note1",
value: "note text",
resource_id: "resource1",
resource_type: "type",
author: { id: "admin_user" },
}
describe("/admin/notes", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/notes/:id", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(Note, note)
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("properly retrieves note", async () => {
const api = useApi()
const response = await api.get("/admin/notes/note1", {
headers: {
authorization: "Bearer test_token",
},
})
expect(response.data).toMatchObject({
note: {
id: "note1",
resource_id: "resource1",
resource_type: "type",
value: "note text",
author: { id: "admin_user" },
},
})
})
})
describe("POST /admin/notes", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("creates a note", async () => {
const api = useApi()
const response = await api
.post(
"/admin/notes",
{
resource_id: "resource-id",
resource_type: "resource-type",
value: "my note",
},
{
headers: {
authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(response.data).toMatchObject({
note: {
id: expect.stringMatching(/^note_*/),
resource_id: "resource-id",
resource_type: "resource-type",
value: "my note",
author_id: "admin_user",
},
})
})
})
describe("GET /admin/notes", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(Note, { ...note, id: "note1" })
await manager.insert(Note, { ...note, id: "note2" })
await manager.insert(Note, {
...note,
id: "note3",
resource_id: "resource2",
})
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("lists notes only related to wanted resource", async () => {
const api = useApi()
const response = await api
.get("/admin/notes?resource_id=resource1", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.data.notes.length).toEqual(2)
expect(response.data).toMatchObject({
notes: [
{
id: "note1",
resource_id: "resource1",
resource_type: "type",
value: "note text",
author: { id: "admin_user" },
},
{
id: "note2",
resource_id: "resource1",
resource_type: "type",
value: "note text",
author: { id: "admin_user" },
},
],
})
})
})
describe("POST /admin/notes/:id", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(Note, note)
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("updates the content of the note", async () => {
const api = useApi()
await api
.post(
"/admin/notes/note1",
{ value: "new text" },
{
headers: {
authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
const response = await api
.get("/admin/notes/note1", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.data.note.value).toEqual("new text")
})
})
describe("DELETE /admin/notes/:id", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(Note, note)
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("deletes the wanted note", async () => {
const api = useApi()
await api
.delete("/admin/notes/note1", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
let error
await api
.get("/admin/notes/note1", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => (error = err))
expect(error.response.status).toEqual(404)
})
})
})
File diff suppressed because it is too large. Load diff
+632 -121
View File
@@ -1,52 +1,336 @@
const path = require("path");
const path = require("path")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder");
const productSeeder = require("../../helpers/product-seeder");
const adminSeeder = require("../../helpers/admin-seeder")
const productSeeder = require("../../helpers/product-seeder")
jest.setTimeout(30000);
jest.setTimeout(50000)
describe("/admin/products", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
const db = useDb()
await db.shutdown()
medusaProcess.kill();
});
medusaProcess.kill()
})
describe("GET /admin/products", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("returns a list of products with all statuses when no status or invalid status is provided", async () => {
const api = useApi()
const res = await api
.get("/admin/products?status%5B%5D=null", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(res.status).toEqual(200)
expect(res.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-product",
status: "draft",
}),
expect.objectContaining({
id: "test-product1",
status: "draft",
}),
])
)
})
it("returns a list of products where status is proposed", async () => {
const api = useApi()
const payload = {
status: "proposed",
}
//update test-product status to proposed
await api
.post("/admin/products/test-product", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
const response = await api
.get("/admin/products?status%5B%5D=proposed", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-product",
status: "proposed",
}),
])
)
})
it("returns a list of products with child entities", async () => {
const api = useApi()
const response = await api
.get("/admin/products", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.data.products).toMatchSnapshot([
{
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
options: [
{
id: expect.stringMatching(/^test-*/),
product_id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
images: [
{
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
variants: [
{
id: "test-variant", //expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_2", //expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_1", // expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
],
tags: [
{
id: expect.stringMatching(/^tag*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
type: {
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
collection: {
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
options: [],
variants: [
{
id: "test-variant_4", //expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_3", //expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
],
tags: [
{
id: expect.stringMatching(/^tag*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
type: {
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
collection: {
id: expect.stringMatching(/^test-*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
])
})
})
describe("POST /admin/products", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection);
await adminSeeder(dbConnection);
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a product", async () => {
const api = useApi();
const api = useApi()
const payload = {
title: "Test product",
title: "Test",
description: "test-product-description",
type: { value: "test-type" },
images: ["test-image.png", "test-image-2.png"],
@@ -61,7 +345,7 @@ describe("/admin/products", () => {
options: [{ value: "large" }, { value: "green" }],
},
],
};
}
const response = await api
.post("/admin/products", payload, {
@@ -70,17 +354,17 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
title: "Test product",
title: "Test",
discountable: true,
is_giftcard: false,
handle: "test-product",
handle: "test",
status: "draft",
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -133,11 +417,77 @@ describe("/admin/products", () => {
}),
],
})
);
});
)
})
it("Sets variant ranks when creating a product", async () => {
const api = useApi()
const payload = {
title: "Test product - 1",
description: "test-product-description 1",
type: { value: "test-type 1" },
images: ["test-image.png", "test-image-2.png"],
collection_id: "test-collection",
tags: [{ value: "123" }, { value: "456" }],
options: [{ title: "size" }, { title: "color" }],
variants: [
{
title: "Test variant 1",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }, { value: "green" }],
},
{
title: "Test variant 2",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }, { value: "green" }],
},
],
}
const creationResponse = await api
.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(creationResponse.status).toEqual(200)
const productId = creationResponse.data.product.id
const response = await api
.get(`/admin/products/${productId}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.data.product).toEqual(
expect.objectContaining({
title: "Test product - 1",
variants: [
expect.objectContaining({
title: "Test variant 1",
}),
expect.objectContaining({
title: "Test variant 2",
}),
],
})
)
})
it("creates a giftcard", async () => {
const api = useApi();
const api = useApi()
const payload = {
title: "Test Giftcard",
@@ -151,7 +501,7 @@ describe("/admin/products", () => {
options: [{ value: "100" }],
},
],
};
}
const response = await api
.post("/admin/products", payload, {
@@ -160,21 +510,21 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
title: "Test Giftcard",
discountable: false,
})
);
});
)
})
it("updates a product (update prices, tags, delete collection, delete type, replaces images)", async () => {
const api = useApi();
it("updates a product (update prices, tags, update status, delete collection, delete type, replaces images)", async () => {
const api = useApi()
const payload = {
collection_id: null,
@@ -182,13 +532,20 @@ describe("/admin/products", () => {
variants: [
{
id: "test-variant",
prices: [{ currency_code: "usd", amount: 100, sale_amount: 75 }],
prices: [
{
currency_code: "usd",
amount: 100,
sale_amount: 75,
},
],
},
],
tags: [{ value: "123" }],
images: ["test-image-2.png"],
type: { value: "test-type-2" },
};
status: "published",
}
const response = await api
.post("/admin/products/test-product", payload, {
@@ -197,10 +554,10 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
@@ -226,20 +583,94 @@ describe("/admin/products", () => {
}),
],
type: null,
status: "published",
collection: null,
type: expect.objectContaining({
value: "test-type-2",
}),
})
);
});
)
})
it("fails to update product with invalid status", async () => {
const api = useApi()
const payload = {
status: null,
}
try {
await api.post("/admin/products/test-product", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
} catch (e) {
expect(e.response.status).toEqual(400)
expect(e.response.data.type).toEqual("invalid_data")
}
})
it("updates a product (variant ordering)", async () => {
const api = useApi()
const payload = {
collection_id: null,
type: null,
variants: [
{
id: "test-variant",
},
{
id: "test-variant_1",
},
{
id: "test-variant_2",
},
],
}
const response = await api
.post("/admin/products/test-product", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
title: "Test product",
variants: [
expect.objectContaining({
id: "test-variant",
title: "Test variant",
}),
expect.objectContaining({
id: "test-variant_1",
title: "Test variant rank (1)",
}),
expect.objectContaining({
id: "test-variant_2",
title: "Test variant rank (2)",
}),
],
type: null,
collection: null,
})
)
})
it("add option", async () => {
const api = useApi();
const api = useApi()
const payload = {
title: "should_add",
};
}
const response = await api
.post("/admin/products/test-product/options", payload, {
@@ -248,41 +679,41 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
options: [
options: expect.arrayContaining([
expect.objectContaining({
title: "should_add",
product_id: "test-product",
}),
],
]),
})
);
});
});
)
})
})
describe("testing for soft-deletion + uniqueness on handles, collection and variant properties", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection);
await adminSeeder(dbConnection);
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("successfully deletes a product", async () => {
const api = useApi();
const api = useApi()
const response = await api
.delete("/admin/products/test-product", {
@@ -291,21 +722,21 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data).toEqual(
expect.objectContaining({
id: "test-product",
deleted: true,
})
);
});
)
})
it("successfully creates product with soft-deleted product handle", async () => {
const api = useApi();
it("successfully creates product with soft-deleted product handle and deletes it again", async () => {
const api = useApi()
// First we soft-delete the product
const response = await api
@@ -315,11 +746,11 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.id).toEqual("test-product");
expect(response.status).toEqual(200)
expect(response.data.id).toEqual("test-product")
// Lets try to create a product with same handle as deleted one
const payload = {
@@ -339,20 +770,70 @@ describe("/admin/products", () => {
options: [{ value: "large" }, { value: "green" }],
},
],
};
}
const res = await api.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
});
})
expect(res.status).toEqual(200);
expect(res.data.product.handle).toEqual("test-product");
});
expect(res.status).toEqual(200)
expect(res.data.product.handle).toEqual("test-product")
// Delete product again to ensure uniqueness is enforced in all cases
const response2 = await api
.delete("/admin/products/test-product", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response2.status).toEqual(200)
expect(response2.data.id).toEqual("test-product")
})
it("should fail when creating a product with a handle that already exists", async () => {
const api = useApi()
// Lets try to create a product with same handle as deleted one
const payload = {
title: "Test product",
handle: "test-product",
description: "test-product-description",
type: { value: "test-type" },
images: ["test-image.png", "test-image-2.png"],
collection_id: "test-collection",
tags: [{ value: "123" }, { value: "456" }],
options: [{ title: "size" }, { title: "color" }],
variants: [
{
title: "Test variant",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }, { value: "green" }],
},
],
}
try {
await api.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
} catch (error) {
expect(error.response.data.message).toMatch(
/duplicate key value violates unique constraint/i
)
}
})
it("successfully deletes product collection", async () => {
const api = useApi();
const api = useApi()
// First we soft-delete the product collection
const response = await api
@@ -362,15 +843,15 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.id).toEqual("test-collection");
});
expect(response.status).toEqual(200)
expect(response.data.id).toEqual("test-collection")
})
it("successfully creates soft-deleted product collection", async () => {
const api = useApi();
const api = useApi()
const response = await api
.delete("/admin/collections/test-collection", {
@@ -379,30 +860,62 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.id).toEqual("test-collection");
expect(response.status).toEqual(200)
expect(response.data.id).toEqual("test-collection")
// Lets try to create a product collection with same handle as deleted one
const payload = {
title: "Another test collection",
handle: "test-collection",
};
}
const res = await api.post("/admin/collections", payload, {
headers: {
Authorization: "Bearer test_token",
},
});
})
expect(res.status).toEqual(200);
expect(res.data.collection.handle).toEqual("test-collection");
});
expect(res.status).toEqual(200)
expect(res.data.collection.handle).toEqual("test-collection")
})
it("should fail when creating a collection with a handle that already exists", async () => {
const api = useApi()
// Lets try to create a collection with same handle as deleted one
const payload = {
title: "Another test collection",
handle: "test-collection",
}
try {
await api.post("/admin/collections", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
} catch (error) {
expect(error.response.data.message).toMatch(
/duplicate key value violates unique constraint/i
)
}
})
it("successfully creates soft-deleted product variant", async () => {
const api = useApi();
const api = useApi()
const product = await api
.get("/admin/products/test-product", {
headers: {
Authorization: "bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
const response = await api
.delete("/admin/products/test-product/variants/test-variant", {
@@ -411,13 +924,12 @@ describe("/admin/products", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data.variant_id).toEqual("test-variant");
expect(response.status).toEqual(200)
expect(response.data.variant_id).toEqual("test-variant")
// Lets try to create a product collection with same handle as deleted one
const payload = {
title: "Second variant",
sku: "test-sku",
@@ -430,19 +942,18 @@ describe("/admin/products", () => {
amount: 100,
},
],
};
options: [{ option_id: "test-option", value: "inserted value" }],
}
const res = await api.post(
"/admin/products/test-product/variants",
payload,
{
const res = await api
.post("/admin/products/test-product/variants", payload, {
headers: {
Authorization: "Bearer test_token",
},
}
);
})
.catch((err) => console.log(err))
expect(res.status).toEqual(200);
expect(res.status).toEqual(200)
expect(res.data.product.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -453,7 +964,7 @@ describe("/admin/products", () => {
barcode: "test-barcode",
}),
])
);
});
});
});
)
})
})
})
@@ -1,53 +1,55 @@
const path = require("path");
const { match } = require("assert")
const path = require("path")
const { RepositoryNotTreeError } = require("typeorm")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder");
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/return-reasons", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
const db = useDb()
await db.shutdown()
medusaProcess.kill();
});
medusaProcess.kill()
})
describe("POST /admin/return-reasons", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await adminSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("creates a return_reason", async () => {
const api = useApi();
const api = useApi()
const payload = {
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
};
}
const response = await api
.post("/admin/return-reasons", payload, {
@@ -56,10 +58,172 @@ describe("/admin/return-reasons", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.return_reason).toMatchSnapshot({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
parent_return_reason: null,
parent_return_reason_id: null,
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
})
})
it("creates a nested return reason", async () => {
const api = useApi()
const payload = {
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
})
)
const nested_payload = {
parent_return_reason_id: response.data.return_reason.id,
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}
const nested_response = await api
.post("/admin/return-reasons", nested_payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(nested_response.status).toEqual(200)
expect(nested_response.data.return_reason).toEqual(
expect.objectContaining({
parent_return_reason_id: response.data.return_reason.id,
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
})
)
})
it("fails to create a doubly nested return reason", async () => {
expect.assertions(5)
const api = useApi()
const payload = {
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
})
)
const nested_payload = {
parent_return_reason_id: response.data.return_reason.id,
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}
const nested_response = await api
.post("/admin/return-reasons", nested_payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
const dbl_nested_payload = {
parent_return_reason_id: nested_response.data.return_reason.id,
label: "Too large size",
description: "Use this if the size was too big",
value: "large_size",
}
const dbl_nested_response = await api
.post("/admin/return-reasons", dbl_nested_payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
expect(err.response.status).toEqual(400)
expect(err.response.data.type).toEqual("invalid_data")
expect(err.response.data.message).toEqual(
"Doubly nested return reasons is not supported"
)
})
})
it("deletes a return_reason", async () => {
const api = useApi()
const payload = {
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
@@ -67,17 +231,37 @@ describe("/admin/return-reasons", () => {
description: "Use this if the size was too big",
value: "too_big",
})
);
});
)
const deleteResponse = await api
.delete(`/admin/return-reasons/${response.data.return_reason.id}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(deleteResponse.data).toEqual(
expect.objectContaining({
id: response.data.return_reason.id,
object: "return_reason",
deleted: true,
})
)
})
it("update a return reason", async () => {
const api = useApi();
const api = useApi()
const payload = {
label: "Too Big Typo",
description: "Use this if the size was too big",
value: "too_big",
};
}
const response = await api
.post("/admin/return-reasons", payload, {
@@ -86,10 +270,10 @@ describe("/admin/return-reasons", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
@@ -97,7 +281,7 @@ describe("/admin/return-reasons", () => {
description: "Use this if the size was too big",
value: "too_big",
})
);
)
const newResponse = await api
.post(
@@ -113,8 +297,8 @@ describe("/admin/return-reasons", () => {
}
)
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(newResponse.data.return_reason).toEqual(
expect.objectContaining({
@@ -122,17 +306,81 @@ describe("/admin/return-reasons", () => {
description: "new desc",
value: "too_big",
})
);
});
)
})
it("lists nested return reasons", async () => {
const api = useApi()
const payload = {
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
const nested_payload = {
parent_return_reason_id: response.data.return_reason.id,
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}
const resp = await api
.post("/admin/return-reasons", nested_payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
const nested_response = await api
.get("/admin/return-reasons", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(nested_response.status).toEqual(200)
expect(nested_response.data.return_reasons).toEqual([
expect.objectContaining({
label: "Wrong size",
description: "Use this if the size was too big",
value: "wrong_size",
return_reason_children: expect.arrayContaining([
expect.objectContaining({
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}),
]),
}),
])
})
it("list return reasons", async () => {
const api = useApi();
const api = useApi()
const payload = {
label: "Too Big Typo",
description: "Use this if the size was too big",
value: "too_big",
};
}
await api
.post("/admin/return-reasons", payload, {
@@ -141,8 +389,8 @@ describe("/admin/return-reasons", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
const response = await api
.get("/admin/return-reasons", {
@@ -151,15 +399,191 @@ describe("/admin/return-reasons", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.status).toEqual(200)
expect(response.data.return_reasons).toEqual([
expect.objectContaining({
value: "too_big",
}),
]);
});
});
});
])
})
})
describe("DELETE /admin/return-reasons", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("deletes single return reason", async () => {
expect.assertions(6)
const api = useApi()
const payload = {
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
})
)
const deleteResult = await api.delete(
`/admin/return-reasons/${response.data.return_reason.id}`,
{
headers: {
Authorization: "Bearer test_token",
},
}
)
expect(deleteResult.status).toEqual(200)
expect(deleteResult.data).toEqual({
id: response.data.return_reason.id,
object: "return_reason",
deleted: true,
})
const getResult = await api
.get(`/admin/return-reasons/${response.data.return_reason.id}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
expect(err.response.status).toEqual(404)
expect(err.response.data.type).toEqual("not_found")
})
})
it("deletes cascade through nested return reasons", async () => {
expect.assertions(10)
const api = useApi()
const payload = {
label: "Wrong Size",
description: "Use this if the size was wrong",
value: "wrong_size",
}
const response = await api
.post("/admin/return-reasons", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
label: "Wrong Size",
description: "Use this if the size was wrong",
value: "wrong_size",
})
)
const payload_child = {
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
parent_return_reason_id: response.data.return_reason.id,
}
const response_child = await api
.post("/admin/return-reasons", payload_child, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response_child.status).toEqual(200)
expect(response_child.data.return_reason).toEqual(
expect.objectContaining({
label: "Too Big",
description: "Use this if the size was too big",
value: "too_big",
parent_return_reason_id: response.data.return_reason.id,
})
)
const deleteResult = await api
.delete(`/admin/return-reasons/${response.data.return_reason.id}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err.response.data)
})
expect(deleteResult.status).toEqual(200)
expect(deleteResult.data).toEqual({
id: response.data.return_reason.id,
object: "return_reason",
deleted: true,
})
await api
.get(`/admin/return-reasons/${response.data.return_reason.id}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
expect(err.response.status).toEqual(404)
expect(err.response.data.type).toEqual("not_found")
})
await api
.get(`/admin/return-reasons/${response_child.data.return_reason.id}`, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
expect(err.response.status).toEqual(404)
expect(err.response.data.type).toEqual("not_found")
})
})
})
})
@@ -0,0 +1,386 @@
const path = require("path")
const {
Region,
ShippingProfile,
ShippingOption,
ShippingOptionRequirement,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const shippingOptionSeeder = require("../../helpers/shipping-option-seeder")
jest.setTimeout(30000)
describe("/admin/shipping-options", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /admin/shipping-options/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await shippingOptionSeeder(dbConnection)
} catch (err) {
console.error(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("updates a shipping option with no existing requirements", async () => {
const api = useApi()
const payload = {
name: "Test option",
amount: 100,
requirements: [
{
type: "min_subtotal",
amount: 1,
},
{
type: "max_subtotal",
amount: 2,
},
],
}
const res = await api.post(`/admin/shipping-options/test-out`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
const requirements = res.data.shipping_option.requirements
expect(res.status).toEqual(200)
expect(requirements.length).toEqual(2)
expect(requirements[0]).toEqual(
expect.objectContaining({
type: "min_subtotal",
shipping_option_id: "test-out",
amount: 1,
})
)
expect(requirements[1]).toEqual(
expect.objectContaining({
type: "max_subtotal",
shipping_option_id: "test-out",
amount: 2,
})
)
})
it("fails as it is not allowed to set id from client side", async () => {
const api = useApi()
const payload = {
name: "Test option",
amount: 100,
requirements: [
{
id: "not_allowed",
type: "min_subtotal",
amount: 1,
},
{
id: "really_not_allowed",
type: "max_subtotal",
amount: 2,
},
],
}
const res = await api
.post(`/admin/shipping-options/test-out`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
return err.response
})
expect(res.status).toEqual(400)
expect(res.data.message).toEqual("ID does not exist")
})
it("it succesfully updates a set of existing requirements", async () => {
const api = useApi()
const payload = {
requirements: [
{
id: "option-req",
type: "min_subtotal",
amount: 15,
},
{
id: "option-req-2",
type: "max_subtotal",
amount: 20,
},
],
amount: 200,
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err.response.data.message)
})
expect(res.status).toEqual(200)
})
it("it succesfully updates a set of existing requirements by updating one and deleting the other", async () => {
const api = useApi()
const payload = {
requirements: [
{
id: "option-req",
type: "min_subtotal",
amount: 15,
},
],
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err.response.data.message)
})
expect(res.status).toEqual(200)
})
it("succesfully updates a set of requirements because max. subtotal >= min. subtotal", async () => {
const api = useApi()
const payload = {
requirements: [
{
id: "option-req",
type: "min_subtotal",
amount: 150,
},
{
id: "option-req-2",
type: "max_subtotal",
amount: 200,
},
],
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err.response.data.message)
})
expect(res.status).toEqual(200)
expect(res.data.shipping_option.requirements[0].amount).toEqual(150)
expect(res.data.shipping_option.requirements[1].amount).toEqual(200)
})
it("fails to updates a set of requirements because max. subtotal <= min. subtotal", async () => {
const api = useApi()
const payload = {
requirements: [
{
id: "option-req",
type: "min_subtotal",
amount: 1500,
},
{
id: "option-req-2",
type: "max_subtotal",
amount: 200,
},
],
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
return err.response
})
expect(res.status).toEqual(400)
expect(res.data.message).toEqual(
"Max. subtotal must be greater than Min. subtotal"
)
})
})
describe("POST /admin/shipping-options", () => {
let payload
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await shippingOptionSeeder(dbConnection)
const api = useApi()
await api.post(
`/admin/regions/region`,
{
fulfillment_providers: ["test-ful"],
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
const manager = dbConnection.manager
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
})
payload = {
name: "Test option",
amount: 100,
price_type: "flat_rate",
region_id: "region",
provider_id: "test-ful",
data: {},
profile_id: defaultProfile.id,
}
} catch (err) {
console.error(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("creates a shipping option with requirements", async () => {
const api = useApi()
payload.requirements = [
{
type: "max_subtotal",
amount: 2,
},
{
type: "min_subtotal",
amount: 1,
},
]
const res = await api.post(`/admin/shipping-options`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
expect(res.status).toEqual(200)
expect(res.data.shipping_option.requirements.length).toEqual(2)
})
it("creates a shipping option with no requirements", async () => {
const api = useApi()
const res = await api.post(`/admin/shipping-options`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
expect(res.status).toEqual(200)
expect(res.data.shipping_option.requirements.length).toEqual(0)
})
it("fails on same requirement types", async () => {
const api = useApi()
payload.requirements = [
{
type: "max_subtotal",
amount: 2,
},
{
type: "max_subtotal",
amount: 1,
},
]
try {
await api.post(`/admin/shipping-options`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
} catch (error) {
expect(error.response.data.message).toEqual(
"Only one requirement of each type is allowed"
)
}
})
it("fails when min_subtotal > max_subtotal", async () => {
const api = useApi()
payload.requirements = [
{
type: "max_subtotal",
amount: 2,
},
{
type: "min_subtotal",
amount: 4,
},
]
try {
await api.post(`/admin/shipping-options`, payload, {
headers: {
Authorization: "Bearer test_token",
},
})
} catch (error) {
expect(error.response.data.message).toEqual(
"Max. subtotal must be greater than Min. subtotal"
)
}
})
})
})
+56 -56
View File
@@ -1,49 +1,49 @@
const path = require("path");
const path = require("path")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const orderSeeder = require("../../helpers/order-seeder");
const swapSeeder = require("../../helpers/swap-seeder");
const adminSeeder = require("../../helpers/admin-seeder");
const orderSeeder = require("../../helpers/order-seeder")
const swapSeeder = require("../../helpers/swap-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/swaps", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
medusaProcess.kill();
});
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/swaps/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await orderSeeder(dbConnection);
await swapSeeder(dbConnection);
await adminSeeder(dbConnection)
await orderSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("gets a swap with cart and totals", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/swaps/test-swap", {
@@ -52,46 +52,46 @@ describe("/admin/swaps", () => {
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.swap).toEqual(
expect.objectContaining({
id: "test-swap",
})
);
)
expect(response.data.swap.cart).toEqual(
expect.objectContaining({
id: "test-cart",
id: "test-cart-w-swap",
shipping_total: 1000,
subtotal: 1000,
total: 2000,
})
);
expect(response.data.swap.cart).toHaveProperty("discount_total");
expect(response.data.swap.cart).toHaveProperty("gift_card_total");
});
});
)
expect(response.data.swap.cart).toHaveProperty("discount_total")
expect(response.data.swap.cart).toHaveProperty("gift_card_total")
})
})
describe("GET /admin/swaps/", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await orderSeeder(dbConnection);
await swapSeeder(dbConnection);
await adminSeeder(dbConnection)
await orderSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
throw err;
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("lists all swaps", async () => {
const api = useApi();
const api = useApi()
const response = await api
.get("/admin/swaps/", {
@@ -100,18 +100,18 @@ describe("/admin/swaps", () => {
},
})
.catch((err) => {
console.log(err);
});
console.log(err)
})
expect(response.status).toEqual(200);
expect(response.data).toHaveProperty("count");
expect(response.data.offset).toBe(0);
expect(response.data.limit).toBe(50);
expect(response.status).toEqual(200)
expect(response.data).toHaveProperty("count")
expect(response.data.offset).toBe(0)
expect(response.data.limit).toBe(50)
expect(response.data.swaps).toContainEqual(
expect.objectContaining({
id: "test-swap",
})
);
});
});
});
)
})
})
})
@@ -0,0 +1,153 @@
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const productSeeder = require("../../helpers/product-seeder")
jest.setTimeout(30000)
describe("/admin/products", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/product-variants", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("lists all product variants", async () => {
const api = useApi()
const response = await api
.get("/admin/variants/", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.variants).toEqual(
expect.arrayContaining([
expect.objectContaining(
{
id: "test-variant",
},
{
id: "test-variant_2",
},
{
id: "test-variant_1",
}
),
])
)
})
it("lists all product variants matching a specific sku", async () => {
const api = useApi()
const response = await api
.get("/admin/variants?q=sku2", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.variants.length).toEqual(1)
expect(response.data.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
sku: "test-sku2",
}),
])
)
})
it("lists all product variants matching a specific variant title", async () => {
const api = useApi()
const response = await api
.get("/admin/variants?q=rank (1)", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.variants.length).toEqual(1)
expect(response.data.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-variant_1",
sku: "test-sku1",
}),
])
)
})
it("lists all product variants matching a specific product title", async () => {
const api = useApi()
const response = await api
.get("/admin/variants?q=Test product1", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.variants.length).toEqual(2)
expect(response.data.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
product_id: "test-product1",
id: "test-variant_3",
sku: "test-sku3",
}),
expect.objectContaining({
product_id: "test-product1",
id: "test-variant_4",
sku: "test-sku4",
}),
])
)
})
})
})
@@ -8,6 +8,14 @@ Object {
}
`;
exports[`/store/carts POST /store/carts/:id fails to complete swap cart with items inventory not/partially covered 1`] = `
Object {
"code": "insufficient_inventory",
"message": "Variant with id: test-variant-2 does not have the required inventory",
"type": "not_allowed",
}
`;
exports[`/store/carts POST /store/carts/:id returns early, if cart is already completed 1`] = `
Object {
"code": "cart_incompatible_state",
@@ -0,0 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/store/variants /test-variant 1`] = `
Object {
"variant": Object {
"allow_backorder": false,
"barcode": "test-barcode",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean",
"height": null,
"hs_code": null,
"id": "test-variant",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price",
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
],
"product": Any<Object>,
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
}
`;
exports[`/store/variants includes default relations 1`] = `
Object {
"variants": Array [
Object {
"allow_backorder": false,
"barcode": "test-barcode",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean",
"height": null,
"hs_code": null,
"id": "test-variant",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price",
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
],
"product": Any<Object>,
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
}
`;
@@ -0,0 +1,245 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/store/products /store/products/:id includes default relations 1`] = `
Object {
"product": Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"title": "Test collection",
"updated_at": Any<String>,
},
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product",
"height": null,
"hs_code": null,
"id": "test-product",
"images": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-image",
"metadata": null,
"updated_at": Any<String>,
"url": "test-image.png",
},
],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-option",
"metadata": null,
"product_id": "test-product",
"title": "test-option",
"updated_at": Any<String>,
"values": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option-1",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant 1",
"variant_id": "test-variant_1",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option-2",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant 2",
"variant_id": "test-variant_2",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option-3",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant 3",
"variant_id": "test-variant_3",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option-4",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant 4",
"variant_id": "test-variant_4",
},
],
},
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "tag1",
"metadata": null,
"updated_at": Any<String>,
"value": "123",
},
],
"thumbnail": null,
"title": "Test product",
"type": Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-type",
"metadata": null,
"updated_at": Any<String>,
"value": "test-type",
},
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": "test-barcode",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean",
"height": null,
"hs_code": null,
"id": "test-variant",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price",
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
],
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"allow_backorder": false,
"barcode": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean2",
"height": null,
"hs_code": null,
"id": "test-variant_2",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price2",
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": "test-variant_2",
},
],
"product_id": "test-product",
"sku": "test-sku2",
"title": "Test variant rank (2)",
"upc": "test-upc2",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"allow_backorder": false,
"barcode": "test-barcode 1",
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean1",
"height": null,
"hs_code": null,
"id": "test-variant_1",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price1",
"region_id": null,
"sale_amount": null,
"updated_at": Any<String>,
"variant_id": "test-variant_1",
},
],
"product_id": "test-product",
"sku": "test-sku1",
"title": "Test variant rank (1)",
"upc": "test-upc1",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
},
}
`;
@@ -58,6 +58,7 @@ Object {
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"thumbnail": null,
"title": "test product",
@@ -91,6 +92,7 @@ Object {
"parent_order_id": "test-order",
"swap_id": StringMatching /\\^swap_\\*/,
},
"payment_authorized_at": null,
"payment_id": null,
"region_id": "test-region",
"shipping_address_id": "test-shipping-address",
@@ -227,6 +229,7 @@ Object {
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"thumbnail": null,
"title": "test product",
@@ -260,6 +263,7 @@ Object {
"parent_order_id": "test-order",
"swap_id": StringMatching /\\^swap_\\*/,
},
"payment_authorized_at": null,
"payment_id": null,
"region_id": "test-region",
"shipping_address_id": "test-shipping-address",
@@ -283,7 +287,7 @@ Object {
"created_at": Any<String>,
"currency_code": "usd",
"customer_id": "test-customer",
"display_id": 2,
"display_id": 6,
"draft_order_id": null,
"email": "test@email.com",
"fulfillment_status": "fulfilled",
+281 -188
View File
@@ -1,155 +1,179 @@
const path = require("path");
const { Region, LineItem, GiftCard } = require("@medusajs/medusa");
const path = require("path")
const { Region, LineItem, GiftCard } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const cartSeeder = require("../../helpers/cart-seeder");
const cartSeeder = require("../../helpers/cart-seeder")
const swapSeeder = require("../../helpers/swap-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/store/carts", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
const doAfterEach = async () => {
const db = useDb();
return await db.teardown();
};
const db = useDb()
return await db.teardown()
}
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
try {
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
} catch (error) {
console.log(error)
}
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
medusaProcess.kill();
});
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /store/carts", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
await manager.insert(Region, {
id: "region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
})
await manager.query(
`UPDATE "country" SET region_id='region' WHERE iso_2 = 'us'`
);
});
)
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("creates a cart", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts");
expect(response.status).toEqual(200);
const response = await api.post("/store/carts")
expect(response.status).toEqual(200)
const getRes = await api.post(`/store/carts/${response.data.cart.id}`);
expect(getRes.status).toEqual(200);
});
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
expect(getRes.status).toEqual(200)
})
it("creates a cart with country", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts", {
country_code: "us",
});
expect(response.status).toEqual(200);
expect(response.data.cart.shipping_address.country_code).toEqual("us");
})
expect(response.status).toEqual(200)
expect(response.data.cart.shipping_address.country_code).toEqual("us")
const getRes = await api.post(`/store/carts/${response.data.cart.id}`);
expect(getRes.status).toEqual(200);
});
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
expect(getRes.status).toEqual(200)
})
it("creates a cart with context", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts", {
context: {
test_id: "test",
},
});
expect(response.status).toEqual(200);
})
expect(response.status).toEqual(200)
const getRes = await api.post(`/store/carts/${response.data.cart.id}`);
expect(getRes.status).toEqual(200);
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
expect(getRes.status).toEqual(200)
const cart = getRes.data.cart;
const cart = getRes.data.cart
expect(cart.context).toEqual({
ip: "::ffff:127.0.0.1",
user_agent: "axios/0.21.1",
test_id: "test",
});
});
});
})
})
})
describe("POST /store/carts/:id", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection);
await cartSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
// We were experiencing some issues when having created a cart in a region
// containing multiple countries. At this point, the cart does not have a shipping
// address. Therefore, on subsequent requests to update the cart, the server
// would throw a 500 due to missing shipping address id on insertion.
it("updates a cart, that does not have a shipping address", async () => {
const api = useApi()
const response = await api.post("/store/carts", {
region_id: "test-region-multiple",
})
const getRes = await api.post(`/store/carts/${response.data.cart.id}`, {
region_id: "test-region",
})
expect(getRes.status).toEqual(200)
})
it("fails on apply discount if limit has been reached", async () => {
const api = useApi();
const api = useApi()
try {
await api.post("/store/carts/test-cart", {
discounts: [{ code: "CREATED" }],
});
})
} catch (error) {
expect(error.response.status).toEqual(400);
expect(error.response.status).toEqual(400)
expect(error.response.data.message).toEqual(
"Discount has been used maximum allowed times"
);
)
}
});
})
it("updates cart customer id", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts/test-cart", {
customer_id: "test-customer-2",
});
})
expect(response.status).toEqual(200);
});
expect(response.status).toEqual(200)
})
it("updates address using string id", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts/test-cart", {
billing_address: "test-general-address",
shipping_address: "test-general-address",
});
})
expect(response.data.cart.shipping_address_id).toEqual(
"test-general-address"
);
)
expect(response.data.cart.billing_address_id).toEqual(
"test-general-address"
);
expect(response.status).toEqual(200);
});
)
expect(response.status).toEqual(200)
})
it("updates address", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/carts/test-cart", {
shipping_address: {
@@ -160,14 +184,14 @@ describe("/store/carts", () => {
country_code: "us",
postal_code: "something",
},
});
})
expect(response.data.cart.shipping_address.first_name).toEqual("clark");
expect(response.status).toEqual(200);
});
expect(response.data.cart.shipping_address.first_name).toEqual("clark")
expect(response.status).toEqual(200)
})
it("adds free shipping to cart then removes it again", async () => {
const api = useApi();
const api = useApi()
let cart = await api.post(
"/store/carts/test-cart",
@@ -175,10 +199,10 @@ describe("/store/carts", () => {
discounts: [{ code: "FREE_SHIPPING" }, { code: "CREATED" }],
},
{ withCredentials: true }
);
)
expect(cart.data.cart.shipping_total).toBe(0);
expect(cart.status).toEqual(200);
expect(cart.data.cart.shipping_total).toBe(0)
expect(cart.status).toEqual(200)
cart = await api.post(
"/store/carts/test-cart",
@@ -186,68 +210,68 @@ describe("/store/carts", () => {
discounts: [{ code: "CREATED" }],
},
{ withCredentials: true }
);
)
expect(cart.data.cart.shipping_total).toBe(1000);
expect(cart.status).toEqual(200);
});
expect(cart.data.cart.shipping_total).toBe(1000)
expect(cart.status).toEqual(200)
})
it("complete cart with giftcard total 0", async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
await manager.insert(GiftCard, {
id: "gift_test",
code: "GC_TEST",
value: 20000,
balance: 20000,
region_id: "test-region",
});
})
const api = useApi();
const api = useApi()
await api.post(`/store/carts/test-cart-3`, {
gift_cards: [{ code: "GC_TEST" }],
});
})
const getRes = await api
.post(`/store/carts/test-cart-3/complete`)
.catch((err) => {
console.log(err.response.data);
});
console.log(err.response.data)
})
expect(getRes.status).toEqual(200);
expect(getRes.data.type).toEqual("order");
});
expect(getRes.status).toEqual(200)
expect(getRes.data.type).toEqual("order")
})
it("complete cart with items inventory covered", async () => {
const api = useApi();
const getRes = await api.post(`/store/carts/test-cart-2/complete-cart`);
const api = useApi()
const getRes = await api.post(`/store/carts/test-cart-2/complete-cart`)
expect(getRes.status).toEqual(200);
expect(getRes.status).toEqual(200)
const variantRes = await api.get("/store/variants/test-variant");
expect(variantRes.data.variant.inventory_quantity).toEqual(0);
});
const variantRes = await api.get("/store/variants/test-variant")
expect(variantRes.data.variant.inventory_quantity).toEqual(0)
})
it("returns early, if cart is already completed", async () => {
const manager = dbConnection.manager;
const api = useApi();
const manager = dbConnection.manager
const api = useApi()
await manager.query(
`UPDATE "cart" SET completed_at=current_timestamp WHERE id = 'test-cart-2'`
);
)
try {
await api.post(`/store/carts/test-cart-2/complete-cart`);
await api.post(`/store/carts/test-cart-2/complete-cart`)
} catch (error) {
expect(error.response.data).toMatchSnapshot({
code: "not_allowed",
message: "Cart has already been completed",
code: "cart_incompatible_state",
});
expect(error.response.status).toEqual(409);
})
expect(error.response.status).toEqual(409)
}
});
})
it("fails to complete cart with items inventory not/partially covered", async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
const li = manager.create(LineItem, {
id: "test-item",
@@ -258,37 +282,110 @@ describe("/store/carts", () => {
quantity: 99,
variant_id: "test-variant-2",
cart_id: "test-cart-2",
});
await manager.save(li);
})
await manager.save(li)
const api = useApi();
const api = useApi()
try {
await api.post(`/store/carts/test-cart-2/complete-cart`);
await api.post(`/store/carts/test-cart-2/complete-cart`)
} catch (e) {
expect(e.response.data).toMatchSnapshot({
code: "insufficient_inventory",
});
expect(e.response.status).toBe(409);
})
expect(e.response.status).toBe(409)
}
//check to see if payment has been cancelled
const res = await api.get(`/store/carts/test-cart-2`);
expect(res.data.cart.payment.canceled_at).not.toBe(null);
});
});
//check to see if payment has been cancelled and cart is not completed
const res = await api.get(`/store/carts/test-cart-2`)
expect(res.data.cart.payment.canceled_at).not.toBe(null)
expect(res.data.cart.completed_at).toBe(null)
})
it("fails to complete swap cart with items inventory not/partially covered", async () => {
const manager = dbConnection.manager
const li = manager.create(LineItem, {
id: "test-item",
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 99,
variant_id: "test-variant-2",
cart_id: "swap-cart",
})
await manager.save(li)
await manager.query(
"UPDATE swap SET cart_id='swap-cart' where id='test-swap'"
)
const api = useApi()
try {
await api.post(`/store/carts/swap-cart/complete-cart`)
} catch (e) {
expect(e.response.data).toMatchSnapshot({
code: "insufficient_inventory",
})
expect(e.response.status).toBe(409)
}
//check to see if payment has been cancelled and cart is not completed
const res = await api.get(`/store/carts/swap-cart`)
expect(res.data.cart.payment_authorized_at).toBe(null)
expect(res.data.cart.payment.canceled_at).not.toBe(null)
})
it("successfully completes swap cart with items inventory not/partially covered due to backorder flag", async () => {
const manager = dbConnection.manager
const li = manager.create(LineItem, {
id: "test-item",
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 99,
variant_id: "test-variant-2",
cart_id: "swap-cart",
})
await manager.save(li)
await manager.query(
"UPDATE swap SET cart_id='swap-cart' where id='test-swap'"
)
await manager.query(
"UPDATE swap SET allow_backorder=true where id='test-swap'"
)
await manager.query("DELETE FROM payment where swap_id='test-swap'")
const api = useApi()
try {
await api.post(`/store/carts/swap-cart/complete-cart`)
} catch (error) {
console.log(error)
}
//check to see if payment is authorized and cart is completed
const res = await api.get(`/store/carts/swap-cart`)
expect(res.data.cart.payment_authorized_at).not.toBe(null)
expect(res.data.cart.completed_at).not.toBe(null)
})
})
describe("POST /store/carts/:id/shipping-methods", () => {
beforeEach(async () => {
await cartSeeder(dbConnection);
});
await cartSeeder(dbConnection)
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("adds a shipping method to cart", async () => {
const api = useApi();
const api = useApi()
const cartWithShippingMethod = await api.post(
"/store/carts/test-cart/shipping-methods",
@@ -296,16 +393,16 @@ describe("/store/carts", () => {
option_id: "test-option",
},
{ withCredentials: true }
);
)
expect(cartWithShippingMethod.data.cart.shipping_methods).toContainEqual(
expect.objectContaining({ shipping_option_id: "test-option" })
);
expect(cartWithShippingMethod.status).toEqual(200);
});
)
expect(cartWithShippingMethod.status).toEqual(200)
})
it("adds a giftcard to cart, but ensures discount only applied to discountable items", async () => {
const api = useApi();
const api = useApi()
// Add standard line item to cart
await api.post(
@@ -315,7 +412,7 @@ describe("/store/carts", () => {
quantity: 1,
},
{ withCredentials: true }
);
)
// Add gift card to cart
await api.post(
@@ -325,7 +422,7 @@ describe("/store/carts", () => {
quantity: 1,
},
{ withCredentials: true }
);
)
// Add a 10% discount to the cart
const cartWithGiftcard = await api.post(
@@ -334,16 +431,16 @@ describe("/store/carts", () => {
discounts: [{ code: "10PERCENT" }],
},
{ withCredentials: true }
);
)
// Ensure that the discount is only applied to the standard item
expect(cartWithGiftcard.data.cart.total).toBe(1900); // 1000 (giftcard) + 900 (standard item with 10% discount)
expect(cartWithGiftcard.data.cart.discount_total).toBe(100);
expect(cartWithGiftcard.status).toEqual(200);
});
expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount)
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
expect(cartWithGiftcard.status).toEqual(200)
})
it("adds no more than 1 shipping method per shipping profile", async () => {
const api = useApi();
const api = useApi()
const addShippingMethod = async (option_id) => {
return await api.post(
"/store/carts/test-cart/shipping-methods",
@@ -351,17 +448,17 @@ describe("/store/carts", () => {
option_id,
},
{ withCredentials: true }
);
};
)
}
await addShippingMethod("test-option");
await addShippingMethod("test-option")
const cartWithAnotherShippingMethod = await addShippingMethod(
"test-option-2"
);
)
expect(
cartWithAnotherShippingMethod.data.cart.shipping_methods.length
).toEqual(1);
).toEqual(1)
expect(
cartWithAnotherShippingMethod.data.cart.shipping_methods
).toContainEqual(
@@ -369,30 +466,30 @@ describe("/store/carts", () => {
shipping_option_id: "test-option-2",
price: 500,
})
);
expect(cartWithAnotherShippingMethod.status).toEqual(200);
});
});
)
expect(cartWithAnotherShippingMethod.status).toEqual(200)
})
})
describe("DELETE /store/carts/:id/discounts/:code", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection);
await cartSeeder(dbConnection)
await dbConnection.manager.query(
`INSERT INTO "cart_discounts" (cart_id, discount_id) VALUES ('test-cart', 'free-shipping')`
);
)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("removes free shipping and updates shipping total", async () => {
const api = useApi();
const api = useApi()
const cartWithFreeShipping = await api.post(
"/store/carts/test-cart",
@@ -400,36 +497,36 @@ describe("/store/carts", () => {
discounts: [{ code: "FREE_SHIPPING" }],
},
{ withCredentials: true }
);
)
expect(cartWithFreeShipping.data.cart.shipping_total).toBe(0);
expect(cartWithFreeShipping.status).toEqual(200);
expect(cartWithFreeShipping.data.cart.shipping_total).toBe(0)
expect(cartWithFreeShipping.status).toEqual(200)
const response = await api.delete(
"/store/carts/test-cart/discounts/FREE_SHIPPING"
);
)
expect(response.data.cart.shipping_total).toBe(1000);
expect(response.status).toEqual(200);
});
});
expect(response.data.cart.shipping_total).toBe(1000)
expect(response.status).toEqual(200)
})
})
describe("get-cart with session customer", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection);
await cartSeeder(dbConnection)
} catch (err) {
console.log(err);
throw err;
console.log(err)
throw err
}
});
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("updates empty cart.customer_id on cart retrieval", async () => {
const api = useApi();
const api = useApi()
let customer = await api.post(
"/store/customers",
@@ -440,29 +537,25 @@ describe("/store/carts", () => {
last_name: "oli",
},
{ withCredentials: true }
);
)
const cookie = customer.headers["set-cookie"][0];
const cookie = customer.headers["set-cookie"][0]
const cart = await api.post(
"/store/carts",
{},
{ withCredentials: true }
);
const cart = await api.post("/store/carts", {}, { withCredentials: true })
const response = await api.get(`/store/carts/${cart.data.cart.id}`, {
headers: {
cookie,
},
withCredentials: true,
});
})
expect(response.data.cart.customer_id).toEqual(customer.data.customer.id);
expect(response.status).toEqual(200);
});
expect(response.data.cart.customer_id).toEqual(customer.data.customer.id)
expect(response.status).toEqual(200)
})
it("updates cart.customer_id on cart retrieval if cart.customer_id differ from session customer", async () => {
const api = useApi();
const api = useApi()
let customer = await api.post(
"/store/customers",
@@ -473,15 +566,15 @@ describe("/store/carts", () => {
last_name: "oli",
},
{ withCredentials: true }
);
)
const cookie = customer.headers["set-cookie"][0];
const cookie = customer.headers["set-cookie"][0]
const cart = await api.post("/store/carts");
const cart = await api.post("/store/carts")
const updatedCart = await api.post(`/store/carts/${cart.data.cart.id}`, {
customer_id: "test-customer",
});
})
const response = await api.get(
`/store/carts/${updatedCart.data.cart.id}`,
@@ -490,10 +583,10 @@ describe("/store/carts", () => {
cookie,
},
}
);
)
expect(response.data.cart.customer_id).toEqual(customer.data.customer.id);
expect(response.status).toEqual(200);
});
});
});
expect(response.data.cart.customer_id).toEqual(customer.data.customer.id)
expect(response.status).toEqual(200)
})
})
})
@@ -1,67 +1,67 @@
const path = require("path");
const { Address, Customer } = require("@medusajs/medusa");
const path = require("path")
const { Address, Customer } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const customerSeeder = require("../../helpers/customer-seeder");
const customerSeeder = require("../../helpers/customer-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/store/customers", () => {
let medusaProcess;
let dbConnection;
let medusaProcess
let dbConnection
const doAfterEach = async () => {
const db = useDb();
await db.teardown();
};
const db = useDb()
await db.teardown()
}
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb();
await db.shutdown();
medusaProcess.kill();
});
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /store/customers", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
await manager.insert(Customer, {
id: "test_customer",
first_name: "John",
last_name: "Deere",
email: "john@deere.com",
has_account: true,
});
});
})
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("creates a customer", async () => {
const api = useApi();
const api = useApi()
const response = await api.post("/store/customers", {
first_name: "James",
last_name: "Bond",
email: "james@bond.com",
password: "test",
});
})
expect(response.status).toEqual(200);
expect(response.data.customer).not.toHaveProperty("password_hash");
});
expect(response.status).toEqual(200)
expect(response.data.customer).not.toHaveProperty("password_hash")
})
it("responds 409 on duplicate", async () => {
const api = useApi();
const api = useApi()
const response = await api
.post("/store/customers", {
@@ -70,15 +70,15 @@ describe("/store/customers", () => {
email: "john@deere.com",
password: "test",
})
.catch((err) => err.response);
.catch((err) => err.response)
expect(response.status).toEqual(402);
});
});
expect(response.status).toEqual(402)
})
})
describe("POST /store/customers/:id", () => {
describe("POST /store/customers/me", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
const manager = dbConnection.manager
await manager.insert(Address, {
id: "addr_test",
first_name: "String",
@@ -88,7 +88,7 @@ describe("/store/customers", () => {
postal_code: "1236",
province: "ca",
country_code: "us",
});
})
await manager.insert(Customer, {
id: "test_customer",
@@ -98,26 +98,26 @@ describe("/store/customers", () => {
password_hash:
"c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test"
has_account: true,
});
});
})
})
afterEach(async () => {
await doAfterEach();
});
await doAfterEach()
})
it("updates a customer", async () => {
const api = useApi();
const api = useApi()
const authResponse = await api.post("/store/auth", {
email: "john@deere.com",
password: "test",
});
})
const customerId = authResponse.data.customer.id;
const [authCookie] = authResponse.headers["set-cookie"][0].split(";");
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
`/store/customers/${customerId}`,
`/store/customers/me`,
{
password: "test",
metadata: { key: "value" },
@@ -127,30 +127,30 @@ describe("/store/customers", () => {
Cookie: authCookie,
},
}
);
)
expect(response.status).toEqual(200);
expect(response.data.customer).not.toHaveProperty("password_hash");
expect(response.status).toEqual(200)
expect(response.data.customer).not.toHaveProperty("password_hash")
expect(response.data.customer).toEqual(
expect.objectContaining({
metadata: { key: "value" },
})
);
});
)
})
it("updates customer billing address", async () => {
const api = useApi();
const api = useApi()
const authResponse = await api.post("/store/auth", {
email: "john@deere.com",
password: "test",
});
})
const customerId = authResponse.data.customer.id;
const [authCookie] = authResponse.headers["set-cookie"][0].split(";");
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
`/store/customers/${customerId}`,
`/store/customers/me`,
{
billing_address: {
first_name: "test",
@@ -167,10 +167,10 @@ describe("/store/customers", () => {
Cookie: authCookie,
},
}
);
)
expect(response.status).toEqual(200);
expect(response.data.customer).not.toHaveProperty("password_hash");
expect(response.status).toEqual(200)
expect(response.data.customer).not.toHaveProperty("password_hash")
expect(response.data.customer.billing_address).toEqual(
expect.objectContaining({
first_name: "test",
@@ -181,22 +181,22 @@ describe("/store/customers", () => {
province: "ca",
country_code: "us",
})
);
});
)
})
it("updates customer billing address with string", async () => {
const api = useApi();
const api = useApi()
const authResponse = await api.post("/store/auth", {
email: "john@deere.com",
password: "test",
});
})
const customerId = authResponse.data.customer.id;
const [authCookie] = authResponse.headers["set-cookie"][0].split(";");
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
`/store/customers/${customerId}`,
`/store/customers/me`,
{
billing_address: "addr_test",
},
@@ -205,10 +205,10 @@ describe("/store/customers", () => {
Cookie: authCookie,
},
}
);
)
expect(response.status).toEqual(200);
expect(response.data.customer).not.toHaveProperty("password_hash");
expect(response.status).toEqual(200)
expect(response.data.customer).not.toHaveProperty("password_hash")
expect(response.data.customer.billing_address).toEqual(
expect.objectContaining({
first_name: "String",
@@ -219,7 +219,7 @@ describe("/store/customers", () => {
province: "ca",
country_code: "us",
})
);
});
});
});
)
})
})
})
@@ -5,7 +5,6 @@ const { useApi } = require("../../../helpers/use-api");
const { initDb, useDb } = require("../../../helpers/use-db");
const draftOrderSeeder = require("../../helpers/draft-order-seeder");
const { create } = require("domain");
jest.setTimeout(30000);
@@ -0,0 +1,133 @@
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const productSeeder = require("../../helpers/product-seeder")
jest.setTimeout(30000)
describe("/store/variants", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
beforeEach(async () => {
try {
await productSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("includes default relations", async () => {
const api = useApi()
const response = await api.get("/store/variants?ids=test-variant")
expect(response.data).toMatchSnapshot({
variants: [
{
allow_backorder: false,
barcode: "test-barcode",
created_at: expect.any(String),
deleted_at: null,
ean: "test-ean",
height: null,
hs_code: null,
id: "test-variant",
inventory_quantity: 10,
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
origin_country: null,
product_id: "test-product",
sku: "test-sku",
title: "Test variant",
upc: "test-upc",
updated_at: expect.any(String),
weight: null,
width: null,
prices: [
{
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 100,
currency_code: "usd",
deleted_at: null,
id: "test-price",
region_id: null,
sale_amount: null,
variant_id: "test-variant",
},
],
product: expect.any(Object),
},
],
})
})
it("/test-variant", async () => {
const api = useApi()
const response = await api.get("/store/variants/test-variant")
expect(response.data).toMatchSnapshot({
variant: {
allow_backorder: false,
barcode: "test-barcode",
created_at: expect.any(String),
deleted_at: null,
ean: "test-ean",
height: null,
hs_code: null,
id: "test-variant",
inventory_quantity: 10,
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
origin_country: null,
product_id: "test-product",
sku: "test-sku",
title: "Test variant",
upc: "test-upc",
updated_at: expect.any(String),
weight: null,
width: null,
prices: [
{
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 100,
currency_code: "usd",
deleted_at: null,
id: "test-price",
region_id: null,
sale_amount: null,
variant_id: "test-variant",
},
],
product: expect.any(Object),
},
})
})
})
@@ -0,0 +1,300 @@
const { Product } = require("@medusajs/medusa")
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const productSeeder = require("../../helpers/product-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000)
describe("/store/products", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("/store/products/:id", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("includes default relations", async () => {
const api = useApi()
const response = await api.get("/store/products/test-product")
expect(response.data).toMatchSnapshot({
product: {
id: "test-product",
variants: [
{
id: "test-variant",
inventory_quantity: 10,
allow_backorder: false,
title: "Test variant",
sku: "test-sku",
ean: "test-ean",
upc: "test-upc",
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
height: null,
hs_code: null,
origin_country: null,
barcode: "test-barcode",
product_id: "test-product",
created_at: expect.any(String),
updated_at: expect.any(String),
prices: [
{
id: "test-money-amount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 100,
created_at: expect.any(String),
currency_code: "usd",
deleted_at: null,
id: "test-price",
region_id: null,
sale_amount: null,
updated_at: expect.any(String),
variant_id: "test-variant",
},
],
},
{
id: "test-variant_2",
inventory_quantity: 10,
allow_backorder: false,
title: "Test variant rank (2)",
sku: "test-sku2",
ean: "test-ean2",
upc: "test-upc2",
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
height: null,
hs_code: null,
origin_country: null,
barcode: null,
product_id: "test-product",
created_at: expect.any(String),
updated_at: expect.any(String),
prices: [
{
id: "test-money-amount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 100,
created_at: expect.any(String),
currency_code: "usd",
deleted_at: null,
id: "test-price2",
region_id: null,
sale_amount: null,
updated_at: expect.any(String),
variant_id: "test-variant_2",
},
],
},
{
id: "test-variant_1",
inventory_quantity: 10,
allow_backorder: false,
title: "Test variant rank (1)",
sku: "test-sku1",
ean: "test-ean1",
upc: "test-upc1",
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
height: null,
hs_code: null,
origin_country: null,
barcode: "test-barcode 1",
product_id: "test-product",
created_at: expect.any(String),
updated_at: expect.any(String),
prices: [
{
id: "test-money-amount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 100,
created_at: expect.any(String),
currency_code: "usd",
deleted_at: null,
id: "test-price1",
region_id: null,
sale_amount: null,
updated_at: expect.any(String),
variant_id: "test-variant_1",
},
],
},
],
images: [
{
id: "test-image",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
handle: "test-product",
title: "Test product",
profile_id: expect.stringMatching(/^sp_*/),
description: "test-product-description",
collection_id: "test-collection",
collection: {
id: "test-collection",
created_at: expect.any(String),
updated_at: expect.any(String),
},
type: {
id: "test-type",
created_at: expect.any(String),
updated_at: expect.any(String),
},
tags: [
{
id: "tag1",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: "test-option",
values: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
variant_id: "test-variant",
metadata: null,
deleted_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: "test-variant-option-1",
value: "Default variant 1",
option_id: "test-option",
variant_id: "test-variant_1",
metadata: null,
deleted_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: "test-variant-option-2",
value: "Default variant 2",
option_id: "test-option",
variant_id: "test-variant_2",
metadata: null,
deleted_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: "test-variant-option-3",
value: "Default variant 3",
option_id: "test-option",
variant_id: "test-variant_3",
metadata: null,
deleted_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: "test-variant-option-4",
value: "Default variant 4",
option_id: "test-option",
variant_id: "test-variant_4",
metadata: null,
deleted_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
created_at: expect.any(String),
updated_at: expect.any(String),
},
})
})
it("Fetching variant options without additional relation fails", async () => {
const api = useApi()
const response = await api.get("/store/products/test-product")
const product = response.data.product
expect(product.variants.some((variant) => variant.options)).toEqual(false)
})
it("lists all published products", async () => {
const api = useApi()
//update test-product status to published
await api
.post(
"/admin/products/test-product",
{
status: "published",
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
const response = await api.get("/store/products")
expect(response.status).toEqual(200)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-product",
status: "published",
}),
])
)
})
})
})
@@ -26,16 +26,35 @@ describe("/store/return-reasons", () => {
describe("GET /store/return-reasons", () => {
let rrId;
let rrId_1;
let rrId_2;
beforeEach(async () => {
try {
const created = dbConnection.manager.create(ReturnReason, {
value: "too_big",
label: "Too Big",
value: "wrong_size",
label: "Wrong size",
});
const result = await dbConnection.manager.save(created);
rrId = result.id;
const created_child = dbConnection.manager.create(ReturnReason, {
value: "too_big",
label: "Too Big",
parent_return_reason_id: rrId
});
const result_child = await dbConnection.manager.save(created_child);
rrId_1 = result_child.id;
const created_2 = dbConnection.manager.create(ReturnReason, {
value: "too_big_1",
label: "Too Big 1",
});
const result_2 = await dbConnection.manager.save(created_2);
rrId_2 = result_2.id;
} catch (err) {
console.log(err);
throw err;
@@ -59,7 +78,15 @@ describe("/store/return-reasons", () => {
expect(response.data.return_reasons).toEqual([
expect.objectContaining({
id: rrId,
value: "too_big",
value: "wrong_size",
return_reason_children:[expect.objectContaining({
id: rrId_1,
value: "too_big",
}),]
}),
expect.objectContaining({
id: rrId_2,
value: "too_big_1",
}),
]);
});
@@ -8,6 +8,7 @@ const {
Product,
ProductVariant,
ShippingOption,
FulfillmentProvider,
LineItem,
Discount,
DiscountRule,
@@ -37,6 +38,8 @@ describe("/store/carts", () => {
describe("POST /store/returns", () => {
let rrId;
let rrId_child;
let rrResult;
beforeEach(async () => {
const manager = dbConnection.manager;
@@ -44,13 +47,17 @@ describe("/store/carts", () => {
`ALTER SEQUENCE order_display_id_seq RESTART WITH 111`
);
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
await manager.insert(Region, {
id: "region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
await manager.insert(Customer, {
id: "cus_1234",
email: "test@email.com",
@@ -98,10 +105,6 @@ describe("/store/carts", () => {
await manager.save(ord);
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
await manager.insert(Product, {
id: "test-product",
title: "test product",
@@ -147,12 +150,23 @@ describe("/store/carts", () => {
});
const created = dbConnection.manager.create(ReturnReason, {
value: "too_big",
label: "Too Big",
value: "wrong_size",
label: "Wrong Size",
});
const result = await dbConnection.manager.save(created);
rrResult = result
rrId = result.id;
const created_1 = dbConnection.manager.create(ReturnReason, {
value: "too_big",
label: "Too Big",
parent_return_reason_id: rrId,
});
const result_1 = await dbConnection.manager.save(created_1);
rrId_child = result_1.id;
});
afterEach(async () => {
@@ -181,6 +195,59 @@ describe("/store/carts", () => {
expect(response.data.return.refund_amount).toEqual(8000);
});
it("failes to create a return with a reason category", async () => {
const api = useApi();
const response = await api
.post("/store/returns", {
order_id: "order_test",
items: [
{
reason_id: rrId,
note: "TOO small",
item_id: "test-item",
quantity: 1,
},
],
})
.catch((err) => {
return err.response;
});
expect(response.status).toEqual(400);
expect(response.data.message).toEqual('Cannot apply return reason category')
});
it("creates a return with reasons", async () => {
const api = useApi();
const response = await api
.post("/store/returns", {
order_id: "order_test",
items: [
{
reason_id: rrId_child,
note: "TOO small",
item_id: "test-item",
quantity: 1,
},
],
})
.catch((err) => {
console.log(err.response)
return err.response;
});
expect(response.status).toEqual(200);
expect(response.data.return.items).toEqual([
expect.objectContaining({
reason_id: rrId_child,
note: "TOO small",
}),
]);
});
it("creates a return with discount and non-discountable item", async () => {
const api = useApi();
@@ -234,32 +301,5 @@ describe("/store/carts", () => {
expect(response.data.return.refund_amount).toEqual(7000);
});
it("creates a return with reasons", async () => {
const api = useApi();
const response = await api
.post("/store/returns", {
order_id: "order_test",
items: [
{
reason_id: rrId,
note: "TOO small",
item_id: "test-item",
quantity: 1,
},
],
})
.catch((err) => {
return err.response;
});
expect(response.status).toEqual(200);
expect(response.data.return.items).toEqual([
expect.objectContaining({
reason_id: rrId,
note: "TOO small",
}),
]);
});
});
});
@@ -1,11 +1,11 @@
const Scrypt = require("scrypt-kdf");
const { User } = require("@medusajs/medusa");
const Scrypt = require("scrypt-kdf")
const { User } = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const manager = connection.manager
const buf = await Scrypt.kdf("secret_password", { logN: 1, r: 1, p: 1 });
const password_hash = buf.toString("base64");
const buf = await Scrypt.kdf("secret_password", { logN: 1, r: 1, p: 1 })
const password_hash = buf.toString("base64")
await manager.insert(User, {
id: "admin_user",
@@ -13,5 +13,5 @@ module.exports = async (connection, data = {}) => {
api_token: "test_token",
password_hash,
...data,
});
};
})
}
@@ -0,0 +1,100 @@
const { useApi } = require("../../helpers/use-api");
const header = {
headers: {
authorization: "Bearer test_token",
},
};
const resolveCall = async (path, payload, header) => {
const api = useApi();
let res;
try {
const resp = await api.post(path, payload, header);
res = resp.status;
} catch (expectedException) {
try {
res = expectedException.response.status;
} catch (_) {
console.error(expectedException);
}
}
return res;
};
const determineFail = (actual, expected, path) => {
if (expected !== actual) {
console.log(`failed at path : ${path}`);
}
expect(actual).toEqual(expected);
};
/**
* Allows you to wrap a Call function so that you may reuse some input values.
* @param {Function} fun - the function to call with partial information
* @param {Object} input - the constant input which we want to supply now
* @returns
*/
module.exports.partial = function (fun, input = {}) {
return async (remaining) => await fun({ ...remaining, ...input });
};
/**
* Allows you to assert a specific code result from a POST call.
* @param {Object} input - the information needed to make the call
* (path & payload) and the expected code (code)
*/
module.exports.expectPostCallToReturn = async function (
input = {
code,
path,
payload: {},
}
) {
const res = await resolveCall(input.path, input.payload, header);
determineFail(res, input.code, input.path);
};
/**
* Allows you to assert a specific code result from multiple POST
* calls.
* @param {Object} input - the collection of objects to execute
* calls from (col), a function which yields the path (pathf),
* and another one which provides the payload (payloadf), as
* well as the code (code) which we want to assert.
*/
module.exports.expectAllPostCallsToReturn = async function (
input = {
code,
col,
pathf,
payloadf,
}
) {
for (const i of input.col) {
const res = await resolveCall(
input.pathf(i),
input.payloadf ? input.payloadf(i) : {},
header
);
determineFail(res, input.code, input.pathf(i));
}
};
/**
* Allows you to retrieve a specific object the response
* from get call,
* and simultaneously assert that the call was successful.
* @param {Object} param0 - contains the path which to
* call (path), and the object within the response.data (get)
* we want to retrieve.
* @returns {Object} found within response.data corresponding
* to the get parameter provided.
*/
module.exports.callGet = async function ({ path, get }) {
const api = useApi();
const res = await api.get(path, header);
determineFail(res.status, 200, path);
return res?.data[get];
};
+112 -56
View File
@@ -14,31 +14,47 @@ const {
LineItem,
Payment,
PaymentSession,
} = require("@medusajs/medusa");
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const manager = connection.manager
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
})
const gcProfile = await manager.findOne(ShippingProfile, {
type: "gift_card",
});
})
await manager.insert(Address, {
id: "test-general-address",
first_name: "superman",
country_code: "us",
});
})
const r = manager.create(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
})
// Region with multiple countries
const regionWithMultipleCoutries = manager.create(Region, {
id: "test-region-multiple",
name: "Test Region",
currency_code: "eur",
tax_rate: 0,
})
await manager.save(regionWithMultipleCoutries)
await manager.query(
`UPDATE "country" SET region_id='test-region-multiple' WHERE iso_2 = 'no'`
)
await manager.query(
`UPDATE "country" SET region_id='test-region-multiple' WHERE iso_2 = 'dk'`
)
const freeRule = manager.create(DiscountRule, {
id: "free-shipping-rule",
@@ -46,18 +62,18 @@ module.exports = async (connection, data = {}) => {
type: "free_shipping",
value: 100,
allocation: "total",
});
})
const freeDisc = manager.create(Discount, {
id: "free-shipping",
code: "FREE_SHIPPING",
is_dynamic: false,
is_disabled: false,
});
})
freeDisc.regions = [r];
freeDisc.rule = freeRule;
await manager.save(freeDisc);
freeDisc.regions = [r]
freeDisc.rule = freeRule
await manager.save(freeDisc)
const tenPercentRule = manager.create(DiscountRule, {
id: "tenpercent-rule",
@@ -65,25 +81,25 @@ module.exports = async (connection, data = {}) => {
type: "percentage",
value: 10,
allocation: "total",
});
})
const tenPercent = manager.create(Discount, {
id: "10Percent",
code: "10PERCENT",
is_dynamic: false,
is_disabled: false,
});
})
tenPercent.regions = [r];
tenPercent.rule = tenPercentRule;
await manager.save(tenPercent);
tenPercent.regions = [r]
tenPercent.rule = tenPercentRule
await manager.save(tenPercent)
const d = await manager.create(Discount, {
id: "test-discount",
code: "CREATED",
is_dynamic: false,
is_disabled: false,
});
})
const dr = await manager.create(DiscountRule, {
id: "test-discount-rule",
@@ -91,31 +107,31 @@ module.exports = async (connection, data = {}) => {
type: "fixed",
value: 10000,
allocation: "total",
});
})
d.rule = dr;
d.regions = [r];
d.rule = dr
d.regions = [r]
await manager.save(d);
await manager.save(d)
await manager.query(
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
);
)
await manager.insert(Customer, {
id: "test-customer",
email: "test@email.com",
});
})
await manager.insert(Customer, {
id: "test-customer-2",
email: "test-2@email.com",
});
})
await manager.insert(Customer, {
id: "some-customer",
email: "some-customer@email.com",
});
})
await manager.insert(ShippingOption, {
id: "test-option",
@@ -126,7 +142,7 @@ module.exports = async (connection, data = {}) => {
price_type: "flat_rate",
amount: 1000,
data: {},
});
})
await manager.insert(ShippingOption, {
id: "gc-option",
@@ -137,7 +153,7 @@ module.exports = async (connection, data = {}) => {
price_type: "flat_rate",
amount: 0,
data: {},
});
})
await manager.insert(ShippingOption, {
id: "test-option-2",
@@ -148,7 +164,7 @@ module.exports = async (connection, data = {}) => {
price_type: "flat_rate",
amount: 500,
data: {},
});
})
await manager.insert(Product, {
id: "giftcard-product",
@@ -157,7 +173,7 @@ module.exports = async (connection, data = {}) => {
discountable: false,
profile_id: gcProfile.id,
options: [{ id: "denom", title: "Denomination" }],
});
})
await manager.insert(ProductVariant, {
id: "giftcard-denom",
@@ -170,14 +186,14 @@ module.exports = async (connection, data = {}) => {
value: "1000",
},
],
});
})
await manager.insert(Product, {
id: "test-product",
title: "test product",
profile_id: defaultProfile.id,
options: [{ id: "test-option", title: "Size" }],
});
})
await manager.insert(ProductVariant, {
id: "test-variant",
@@ -190,7 +206,7 @@ module.exports = async (connection, data = {}) => {
value: "Size",
},
],
});
})
await manager.insert(ProductVariant, {
id: "test-variant-2",
@@ -203,31 +219,31 @@ module.exports = async (connection, data = {}) => {
value: "Size",
},
],
});
})
const ma = manager.create(MoneyAmount, {
variant_id: "test-variant",
currency_code: "usd",
amount: 1000,
});
})
await manager.save(ma);
await manager.save(ma)
const ma2 = manager.create(MoneyAmount, {
variant_id: "test-variant-2",
currency_code: "usd",
amount: 8000,
});
})
await manager.save(ma2);
await manager.save(ma2)
const ma3 = manager.create(MoneyAmount, {
variant_id: "giftcard-denom",
currency_code: "usd",
amount: 1000,
});
})
await manager.save(ma3);
await manager.save(ma3)
const cart = manager.create(Cart, {
id: "test-cart",
@@ -241,9 +257,9 @@ module.exports = async (connection, data = {}) => {
region_id: "test-region",
currency_code: "usd",
items: [],
});
})
await manager.save(cart);
await manager.save(cart)
const cart2 = manager.create(Cart, {
id: "test-cart-2",
@@ -258,7 +274,26 @@ module.exports = async (connection, data = {}) => {
currency_code: "usd",
completed_at: null,
items: [],
});
})
const swapCart = manager.create(Cart, {
id: "swap-cart",
type: "swap",
customer_id: "some-customer",
email: "some-customer@email.com",
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
region_id: "test-region",
currency_code: "usd",
completed_at: null,
items: [],
metadata: {
swap_id: "test-swap",
},
})
const pay = manager.create(Payment, {
id: "test-payment",
@@ -267,13 +302,25 @@ module.exports = async (connection, data = {}) => {
amount_refunded: 0,
provider_id: "test-pay",
data: {},
});
})
await manager.save(pay);
const swapPay = manager.create(Payment, {
id: "test-swap-payment",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test-pay",
data: {},
})
cart2.payment = pay;
await manager.save(pay)
await manager.save(swapPay)
await manager.save(cart2);
cart2.payment = pay
swapCart.payment = swapPay
await manager.save(cart2)
await manager.save(swapCart)
await manager.insert(PaymentSession, {
id: "test-session",
@@ -282,7 +329,16 @@ module.exports = async (connection, data = {}) => {
is_selected: true,
data: {},
status: "authorized",
});
})
await manager.insert(PaymentSession, {
id: "test-swap-session",
cart_id: "swap-cart",
provider_id: "test-pay",
is_selected: true,
data: {},
status: "authorized",
})
await manager.insert(ShippingMethod, {
id: "test-method",
@@ -290,7 +346,7 @@ module.exports = async (connection, data = {}) => {
cart_id: "test-cart",
price: 1000,
data: {},
});
})
const li = manager.create(LineItem, {
id: "test-item",
@@ -301,8 +357,8 @@ module.exports = async (connection, data = {}) => {
quantity: 1,
variant_id: "test-variant",
cart_id: "test-cart-2",
});
await manager.save(li);
})
await manager.save(li)
const cart3 = manager.create(Cart, {
id: "test-cart-3",
@@ -317,8 +373,8 @@ module.exports = async (connection, data = {}) => {
currency_code: "usd",
completed_at: null,
items: [],
});
await manager.save(cart3);
})
await manager.save(cart3)
await manager.insert(ShippingMethod, {
id: "test-method-2",
@@ -326,7 +382,7 @@ module.exports = async (connection, data = {}) => {
cart_id: "test-cart-3",
price: 0,
data: {},
});
})
const li2 = manager.create(LineItem, {
id: "test-item-2",
@@ -337,6 +393,6 @@ module.exports = async (connection, data = {}) => {
quantity: 1,
variant_id: "test-variant",
cart_id: "test-cart-3",
});
await manager.save(li2);
};
})
await manager.save(li2)
}
@@ -0,0 +1,118 @@
const {
ClaimOrder,
Order,
LineItem,
Fulfillment,
Return,
} = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
let orderWithClaim = manager.create(Order, {
id: "order-with-claim",
customer_id: "test-customer",
email: "test@email.com",
payment_status: "captured",
fulfillment_status: "fulfilled",
billing_address: {
id: "test-billing-address",
first_name: "lebron",
},
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
region_id: "test-region",
currency_code: "usd",
tax_rate: 0,
discounts: [],
payments: [
{
id: "test-payment-for-claim-order",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test-pay",
data: {},
},
],
items: [],
...data,
});
await manager.save(orderWithClaim);
const li = manager.create(LineItem, {
id: "test-item-co-2",
fulfilled_quantity: 1,
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 1,
variant_id: "test-variant",
order_id: orderWithClaim.id,
});
await manager.save(li);
const li2 = manager.create(LineItem, {
id: "test-item-co-3",
fulfilled_quantity: 4,
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 4,
variant_id: "test-variant",
order_id: orderWithClaim.id,
});
await manager.save(li2);
const claimWithFulfillment = manager.create(ClaimOrder, {
id: "claim-w-f",
type: "replace",
payment_status: "na",
fulfillment_status: "not_fulfilled",
order_id: "order-with-claim",
...data,
});
const ful1 = manager.create(Fulfillment, {
id: "fulfillment-co-1",
data: {},
provider_id: "test-ful",
});
const ful2 = manager.create(Fulfillment, {
id: "fulfillment-co-2",
data: {},
provider_id: "test-ful",
});
claimWithFulfillment.fulfillments = [ful1, ful2];
await manager.save(claimWithFulfillment);
const claimWithReturn = manager.create(ClaimOrder, {
id: "claim-w-r",
type: "replace",
payment_status: "na",
fulfillment_status: "not_fulfilled",
order_id: "order-with-claim",
...data,
});
await manager.save(claimWithReturn);
await manager.insert(Return, {
id: "return-id-2",
claim_order_id: "claim-w-r",
status: "requested",
refund_amount: 0,
data: {},
});
};
@@ -1,27 +1,33 @@
const { Customer, Address } = require("@medusajs/medusa");
const { Customer, Address } = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const manager = connection.manager
await manager.insert(Customer, {
id: "test-customer-1",
email: "test1@email.com",
});
})
await manager.insert(Customer, {
id: "test-customer-2",
email: "test2@email.com",
});
})
await manager.insert(Customer, {
id: "test-customer-3",
email: "test3@email.com",
});
})
await manager.insert(Customer, {
id: "test-customer-has_account",
email: "test4@email.com",
has_account: true,
})
await manager.insert(Address, {
id: "test-address",
first_name: "Lebron",
last_name: "James",
customer_id: "test-customer-1",
});
};
})
}
@@ -9,6 +9,7 @@ const {
Product,
ProductVariant,
Region,
Payment,
Order,
Swap,
} = require("@medusajs/medusa");
@@ -183,4 +184,140 @@ module.exports = async (connection, data = {}) => {
price: 1000,
data: {},
});
const orderTemplate = () => {
return {
customer_id: "test-customer",
email: "test@gmail.com",
payment_status: "not_paid",
fulfillment_status: "not_fulfilled",
region_id: "test-region",
currency_code: "usd",
tax_rate: 0,
...data,
};
};
const paymentTemplate = () => {
return {
amount: 10000,
currency_code: "usd",
provider_id: "test-pay",
data: {},
};
};
const orderWithClaim = manager.create(Order, {
id: "test-order-w-c",
claims: [
{
type: "replace",
id: "claim-1",
payment_status: "na",
fulfillment_status: "not_fulfilled",
payment_provider: "test-pay",
},
],
...orderTemplate(),
});
await manager.save(orderWithClaim);
await manager.insert(Payment, {
order_id: "test-order-w-c",
id: "o-pay1",
...paymentTemplate(),
});
const orderWithSwap = manager.create(Order, {
id: "test-order-w-s",
...orderTemplate(),
});
await manager.save(orderWithSwap);
await manager.insert(Payment, {
order_id: "test-order-w-s",
id: "o-pay2",
...paymentTemplate(),
});
const swap1 = manager.create(Swap, {
id: "swap-1",
order_id: "test-order-w-s",
fulfillment_status: "not_fulfilled",
payment_status: "not_paid",
});
const pay1 = manager.create(Payment, {
id: "pay1",
...paymentTemplate(),
});
swap1.payment = pay1;
const swap2 = manager.create(Swap, {
id: "swap-2",
order_id: "test-order-w-s",
fulfillment_status: "not_fulfilled",
payment_status: "not_paid",
});
const pay2 = manager.create(Payment, {
id: "pay2",
...paymentTemplate(),
});
swap2.payment = pay2;
await manager.save(swap1);
await manager.save(swap2);
const orderWithFulfillment = manager.create(Order, {
id: "test-order-w-f",
fulfillments: [
{
id: "fulfillment-on-order-1",
data: {},
provider_id: "test-ful",
},
{
id: "fulfillment-on-order-2",
data: {},
provider_id: "test-ful",
},
],
...orderTemplate(),
});
await manager.save(orderWithFulfillment);
await manager.insert(Payment, {
order_id: "test-order-w-f",
id: "o-pay3",
...paymentTemplate(),
});
const orderWithReturn = manager.create(Order, {
id: "test-order-w-r",
returns: [
{
id: "return-on-order-1",
refund_amount: 0,
},
{
id: "return-on-order-2",
refund_amount: 0,
},
],
...orderTemplate(),
});
await manager.save(orderWithReturn);
await manager.insert(Payment, {
order_id: "test-order-w-r",
id: "o-pay4",
...paymentTemplate(),
});
};
+136 -20
View File
@@ -2,55 +2,56 @@ const {
ProductCollection,
ProductTag,
ProductType,
ProductOption,
Region,
Product,
ShippingProfile,
ProductVariant,
Image,
} = require("@medusajs/medusa");
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const manager = connection.manager
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
})
const coll = manager.create(ProductCollection, {
id: "test-collection",
handle: "test-collection",
title: "Test collection",
});
})
await manager.save(coll);
await manager.save(coll)
const tag = manager.create(ProductTag, {
id: "tag1",
value: "123",
});
})
await manager.save(tag);
await manager.save(tag)
const type = manager.create(ProductType, {
id: "test-type",
value: "test-type",
});
})
await manager.save(type);
await manager.save(type)
const image = manager.create(Image, {
id: "test-image",
url: "test-image.png",
});
})
await manager.save(image);
await manager.save(image)
await manager.insert(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
})
const p = manager.create(Product, {
id: "test-product",
@@ -64,23 +65,138 @@ module.exports = async (connection, data = {}) => {
{ id: "tag1", value: "123" },
{ tag: "tag2", value: "456" },
],
options: [{ id: "test-option", title: "Default value" }],
});
})
p.images = [image];
p.images = [image]
await manager.save(p);
await manager.save(p)
await manager.insert(ProductVariant, {
await manager.save(ProductOption, {
id: "test-option",
title: "test-option",
product_id: "test-product",
})
const variant1 = await manager.create(ProductVariant, {
id: "test-variant",
inventory_quantity: 10,
title: "Test variant",
variant_rank: 0,
sku: "test-sku",
ean: "test-ean",
upc: "test-upc",
barcode: "test-barcode",
product_id: "test-product",
prices: [{ id: "test-price", currency_code: "usd", amount: 100 }],
options: [{ id: "test-variant-option", value: "Default variant" }],
});
};
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant1)
const variant2 = await manager.create(ProductVariant, {
id: "test-variant_1",
inventory_quantity: 10,
title: "Test variant rank (1)",
variant_rank: 2,
sku: "test-sku1",
ean: "test-ean1",
upc: "test-upc1",
barcode: "test-barcode 1",
product_id: "test-product",
prices: [{ id: "test-price1", currency_code: "usd", amount: 100 }],
options: [
{
id: "test-variant-option-1",
value: "Default variant 1",
option_id: "test-option",
},
],
})
await manager.save(variant2)
const variant3 = await manager.create(ProductVariant, {
id: "test-variant_2",
inventory_quantity: 10,
title: "Test variant rank (2)",
variant_rank: 1,
sku: "test-sku2",
ean: "test-ean2",
upc: "test-upc2",
product_id: "test-product",
prices: [{ id: "test-price2", currency_code: "usd", amount: 100 }],
options: [
{
id: "test-variant-option-2",
value: "Default variant 2",
option_id: "test-option",
},
],
})
await manager.save(variant3)
const p1 = manager.create(Product, {
id: "test-product1",
handle: "test-product1",
title: "Test product1",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
type: { id: "test-type", value: "test-type" },
tags: [
{ id: "tag1", value: "123" },
{ tag: "tag2", value: "456" },
],
})
await manager.save(p1)
const variant4 = await manager.create(ProductVariant, {
id: "test-variant_3",
inventory_quantity: 10,
title: "Test variant rank (2)",
variant_rank: 1,
sku: "test-sku3",
ean: "test-ean3",
upc: "test-upc3",
product_id: "test-product1",
prices: [{ id: "test-price3", currency_code: "usd", amount: 100 }],
options: [
{
id: "test-variant-option-3",
value: "Default variant 3",
option_id: "test-option",
},
],
})
await manager.save(variant4)
const variant5 = await manager.create(ProductVariant, {
id: "test-variant_4",
inventory_quantity: 10,
title: "Test variant rank (2)",
variant_rank: 0,
sku: "test-sku4",
ean: "test-ean4",
upc: "test-upc4",
product_id: "test-product1",
prices: [{ id: "test-price4", currency_code: "usd", amount: 100 }],
options: [
{
id: "test-variant-option-4",
value: "Default variant 4",
option_id: "test-option",
},
],
})
await manager.save(variant5)
}
@@ -0,0 +1,59 @@
const {
Region,
ShippingProfile,
ShippingOption,
ShippingOptionRequirement,
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager
await manager.insert(Region, {
id: "region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
})
await manager.insert(ShippingOption, {
id: "test-out",
name: "Test out",
profile_id: defaultProfile.id,
region_id: "region",
provider_id: "test-ful",
data: {},
price_type: "flat_rate",
amount: 2000,
is_return: false,
})
await manager.insert(ShippingOption, {
id: "test-option-req",
name: "With req",
profile_id: defaultProfile.id,
region_id: "region",
provider_id: "test-ful",
data: {},
price_type: "flat_rate",
amount: 2000,
is_return: false,
})
await manager.insert(ShippingOptionRequirement, {
id: "option-req",
shipping_option_id: "test-option-req",
type: "min_subtotal",
amount: 5,
})
await manager.insert(ShippingOptionRequirement, {
id: "option-req-2",
shipping_option_id: "test-option-req",
type: "max_subtotal",
amount: 10,
})
}
+92 -28
View File
@@ -9,14 +9,15 @@ const {
Product,
ProductVariant,
Region,
Payment,
Order,
Swap,
Cart,
Return,
} = require("@medusajs/medusa");
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const manager = connection.manager
let orderWithSwap = manager.create(Order, {
id: "order-with-swap",
@@ -43,18 +44,18 @@ module.exports = async (connection, data = {}) => {
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test",
provider_id: "test-pay",
data: {},
},
],
items: [],
...data,
});
})
orderWithSwap = await manager.save(orderWithSwap);
orderWithSwap = await manager.save(orderWithSwap)
const cart = manager.create(Cart, {
id: "test-cart",
id: "test-cart-w-swap",
customer_id: "test-customer",
email: "test-customer@email.com",
shipping_address_id: "test-shipping-address",
@@ -65,22 +66,22 @@ module.exports = async (connection, data = {}) => {
swap_id: "test-swap",
parent_order_id: orderWithSwap.id,
},
});
})
await manager.save(cart);
await manager.save(cart)
const swap = manager.create(Swap, {
id: "test-swap",
order_id: "order-with-swap",
payment_status: "captured",
fulfillment_status: "fulfilled",
cart_id: "test-cart",
cart_id: "test-cart-w-swap",
payment: {
id: "test-payment-swap",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test",
provider_id: "test-pay",
data: {},
},
additional_items: [
@@ -93,13 +94,77 @@ module.exports = async (connection, data = {}) => {
unit_price: 9000,
quantity: 1,
variant_id: "test-variant-2",
cart_id: "test-cart",
cart_id: "test-cart-w-swap",
},
],
})
await manager.save(swap)
const cartTemplate = async (cartId) => {
const cart = manager.create(Cart, {
id: cartId,
customer_id: "test-customer",
email: "test-customer@email.com",
shipping_address_id: "test-shipping-address",
billing_address_id: "test-billing-address",
region_id: "test-region",
type: "swap",
metadata: {},
...data,
});
await manager.save(cart);
};
const swapTemplate = async (cartId) => {
await cartTemplate(cartId);
return {
order_id: orderWithSwap.id,
fulfillment_status: "fulfilled",
payment_status: "not_paid",
cart_id: cartId,
payment: {
amount: 5000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test-pay",
data: {},
},
...data,
};
};
const swapWithFulfillments = manager.create(Swap, {
id: "swap-w-f",
fulfillments: [
{
id: "fulfillment-1",
data: {},
provider_id: "test-ful",
},
{
id: "fulfillment-2",
data: {},
provider_id: "test-ful",
},
],
...(await swapTemplate("sc-w-f")),
});
await manager.save(swap);
await manager.save(swapWithFulfillments);
const swapWithReturn = manager.create(Swap, {
id: "swap-w-r",
return_order: {
id: "return-id",
status: "requested",
refund_amount: 0,
},
...(await swapTemplate("sc-w-r")),
});
await manager.save(swapWithReturn);
const li = manager.create(LineItem, {
id: "return-item-1",
fulfilled_quantity: 1,
@@ -111,9 +176,9 @@ module.exports = async (connection, data = {}) => {
variant_id: "test-variant",
order_id: orderWithSwap.id,
cart_id: cart.id,
});
})
await manager.save(li);
await manager.save(li)
const li2 = manager.create(LineItem, {
id: "test-item-many",
@@ -125,34 +190,33 @@ module.exports = async (connection, data = {}) => {
quantity: 4,
variant_id: "test-variant",
order_id: orderWithSwap.id,
});
})
await manager.save(li2);
await manager.save(li2)
const swapReturn = await manager.create(Return, {
swap_id: swap.id,
order_id: orderWithSwap.id,
item_id: li.id,
refund_amount: li.quantity * li.unit_price,
// shipping_method_id: ,
});
})
await manager.save(swapReturn);
await manager.save(swapReturn)
const return_item1 = manager.create(LineItem, {
...li,
unit_price: -1 * li.unit_price,
});
})
await manager.save(return_item1);
await manager.save(return_item1)
await manager.insert(ShippingMethod, {
id: "another-test-method",
shipping_option_id: "test-option",
cart_id: "test-cart",
cart_id: "test-cart-w-swap",
price: 1000,
data: {},
});
})
const swapOnSwap = manager.create(Swap, {
id: "swap-on-swap",
@@ -175,7 +239,7 @@ module.exports = async (connection, data = {}) => {
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test",
provider_id: "test-pay",
data: {},
},
additional_items: [
@@ -190,9 +254,9 @@ module.exports = async (connection, data = {}) => {
variant_id: "test-variant",
},
],
});
})
await manager.save(swapOnSwap);
await manager.save(swapOnSwap)
await manager.insert(ShippingMethod, {
id: "test-method-swap-order",
@@ -200,5 +264,5 @@ module.exports = async (connection, data = {}) => {
order_id: "order-with-swap",
price: 1000,
data: {},
});
};
})
}
+3 -3
View File
@@ -1,5 +1,5 @@
const DB_USERNAME = process.env.DB_USERNAME || "postgres";
const DB_PASSWORD = process.env.DB_PASSWORD || "";
const DB_USERNAME = process.env.DB_USERNAME || "postgres"
const DB_PASSWORD = process.env.DB_PASSWORD || ""
module.exports = {
plugins: [],
@@ -8,4 +8,4 @@ module.exports = {
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost/medusa-integration`,
database_type: "postgres",
},
};
}
+3 -3
View File
@@ -8,15 +8,15 @@
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "1.1.38-dev-1630001256218",
"medusa-interfaces": "1.1.21-dev-1630001256218",
"@medusajs/medusa": "1.1.40-dev-1631630701835",
"medusa-interfaces": "1.1.21-dev-1631630701835",
"typeorm": "^0.2.31"
},
"devDependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "1.1.13-dev-1630001256218",
"babel-preset-medusa-package": "1.1.13-dev-1631630701835",
"jest": "^26.6.3"
}
}
File diff suppressed because it is too large. Load diff
+1
View File
@@ -21,6 +21,7 @@
"import-from": "^3.0.0",
"jest": "^26.6.3",
"lerna": "^3.22.1",
"microbundle": "^0.13.3",
"mongoose": "^5.10.15",
"pg-god": "^1.0.11",
"prettier": "^2.1.1",
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.15](https://github.com/medusajs/medusa/compare/babel-preset-medusa-package@1.1.14...babel-preset-medusa-package@1.1.15) (2021-09-15)
**Note:** Version bump only for package babel-preset-medusa-package
## [1.1.14](https://github.com/medusajs/medusa/compare/babel-preset-medusa-package@1.1.13...babel-preset-medusa-package@1.1.14) (2021-09-14)
**Note:** Version bump only for package babel-preset-medusa-package
## [1.1.13](https://github.com/medusajs/medusa/compare/babel-preset-medusa-package@1.1.12...babel-preset-medusa-package@1.1.13) (2021-07-26)
**Note:** Version bump only for package babel-preset-medusa-package
@@ -1,6 +1,6 @@
{
"name": "babel-preset-medusa-package",
"version": "1.1.13",
"version": "1.1.15",
"author": "Sebastian Rindom <sebastian@mrbltech.com>",
"repository": {
"type": "git",
@@ -26,5 +26,5 @@
"engines": {
"node": ">=10.14.0"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
+3
View File
@@ -0,0 +1,3 @@
node_modules
/dist
yarn.lock
+37
View File
@@ -0,0 +1,37 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.un~
yarn.lock
src
flow-typed
coverage
decls
examples
# tests
__tests__
+23
View File
@@ -0,0 +1,23 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.3](https://github.com/medusajs/medusa/compare/create-medusa-app@0.0.1...create-medusa-app@0.0.3) (2021-09-15)
### Bug Fixes
* flip seed flag to default to true ([#398](https://github.com/medusajs/medusa/issues/398)) ([dd1025f](https://github.com/medusajs/medusa/commit/dd1025fd5369eb264d7d5f5d6db41c888259d786))
* versioning ([3b8901e](https://github.com/medusajs/medusa/commit/3b8901ebc2fb41dc8d5372a808c5eaafd7d32646))
## 0.0.1 (2021-09-14)
### Features
* adds create-medusa-app ([#377](https://github.com/medusajs/medusa/issues/377)) ([ec6d16e](https://github.com/medusajs/medusa/commit/ec6d16e945f4b8a99e9dcc8ae2e92a2318fbc709))
+7
View File
@@ -0,0 +1,7 @@
#! /usr/bin/env node
const { run } = require("./dist")
run().catch((e) => {
console.warn(e)
})
+60
View File
@@ -0,0 +1,60 @@
{
"name": "create-medusa-app",
"version": "0.0.3",
"main": "dist/index.js",
"bin": "cli.js",
"license": "MIT",
"files": [
"dist/index.js",
"cli.js"
],
"scripts": {
"build": "microbundle -i src/index.ts --no-pkg-main --target=node -f=cjs --sourcemap=false --compress --alias worker_threads=@ascorbic/worker-threads-shim",
"watch": "microbundle -i src/index.ts --no-pkg-main --target=node -f=cjs --sourcemap=false --alias worker_threads=@ascorbic/worker-threads-shim --watch",
"prepare": "yarn build"
},
"dependencies": {
"@babel/runtime": "^7.15.4"
},
"devDependencies": {
"@ascorbic/worker-threads-shim": "^1.0.0",
"@babel/runtime": "^7.15.4",
"@types/chalk": "^2.2.0",
"@types/commander": "^2.12.2",
"@types/configstore": "^4.0.0",
"@types/fs-extra": "^9.0.12",
"@types/node": "^14.17.14",
"ansi-wordwrap": "^1.0.2",
"chalk": "^4.1.2",
"commander": "^8.1.0",
"common-tags": "^1.8.0",
"configstore": "^6.0.0",
"enquirer": "^2.3.6",
"eslint": "^7.32.0",
"execa": "^5.1.1",
"fs-exists-cached": "^1.0.0",
"fs-extra": "^10.0.0",
"hosted-git-info": "^4.0.2",
"is-valid-path": "^0.1.1",
"joi": "^17.4.2",
"microbundle": "^0.13.3",
"node-fetch": "^2.6.1",
"prettier": "^2.3.2",
"prompts": "^2.4.1",
"string-length": "^4.0.2",
"terminal-link": "^2.1.1",
"tiny-spin": "^1.0.2",
"url": "^0.11.0",
"uuid": "3.4.0"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa.git",
"directory": "packages/create-medusa-app"
},
"author": "Sebastian Rindom <seb@medusa-commerce.com>",
"publishConfig": {
"access": "public"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -0,0 +1,17 @@
import Configstore from "configstore"
let config: Configstore
export const getConfigStore = (): Configstore => {
if (!config) {
config = new Configstore(
`medusa`,
{},
{
globalConfigPath: true,
}
)
}
return config
}
+133
View File
@@ -0,0 +1,133 @@
import path from "path"
import Commander from "commander"
import chalk from "chalk"
import { prompt } from "enquirer"
import { newStarter } from "./new-starter"
import { track } from "./track"
import pkg from "../package.json"
let projectPath: string = ""
const questions = {
projectRoot: {
type: "input",
name: "projectRoot",
message: "Where should your project be installed?",
initial: "my-medusa-store",
},
starter: {
type: "select",
name: "starter",
message: "Which Medusa starter would you like to install?",
choices: ["medusa-starter-default", "medusa-starter-contentful", "Other"],
},
starterUrl: {
type: "input",
name: "starterUrl",
message: "Where is the starter located? (URL or path)",
},
storefront: {
type: "select",
name: "storefront",
message: "Which storefront starter would you like to install?",
choices: ["Gatsby Starter", "Next.js Starter", "None"],
},
}
const program = new Commander.Command(pkg.name)
.version(pkg.version)
.action((name) => (projectPath = name))
.option(`-r --root`, `The directory to install your Medusa app`)
.option(
`-s --starter-url`,
`A GitHub URL to a repository that contains a Medusa starter project to bootstrap from`
)
.option(
`--no-seed`,
`If run with the no-seed flag the script will skip seeding the database upon setup`
)
.option(`-v --verbose`, `Show all installation output`)
.parse(process.argv)
export const run = async (): Promise<void> => {
track("CREATE_CLI")
if (typeof projectPath === "string") {
projectPath = projectPath.trim()
}
const { projectRoot } = (await prompt(questions.projectRoot)) as {
projectRoot: string
}
let { starter } = (await prompt(questions.starter)) as {
starter: string
}
if (starter === "Other") {
const { starterUrl } = (await prompt(questions.starterUrl)) as {
starterUrl: string
}
starter = starterUrl
} else {
starter = `medusajs/${starter}`
}
track("STARTER_SELECTED", { starter })
const progOptions = program.opts()
const noSeed = progOptions.noSeed
track("SEED_SELECTED", { seed: !noSeed })
const { storefront } = (await prompt(questions.storefront)) as {
storefront: string
}
track("STOREFRONT_SELECTED", { storefront })
await newStarter({
starter,
root: path.join(projectRoot, `backend`),
seed: !noSeed,
verbose: progOptions.verbose,
})
const hasStorefront = storefront.toLowerCase() !== "none"
if (hasStorefront) {
const storefrontStarter =
storefront.toLowerCase() === "gatsby starter"
? "https://github.com/medusajs/gatsby-starter-medusa"
: "https://github.com/medusajs/nextjs-starter-medusa"
await newStarter({
starter: storefrontStarter,
root: path.join(projectRoot, `storefront`),
verbose: progOptions.verbose,
})
}
await newStarter({
starter: "https://github.com/medusajs/admin",
root: path.join(projectRoot, `admin`),
keepGit: true,
verbose: progOptions.verbose,
})
console.log(`
Your project is ready 🚀. The available commands are:
Medusa API
cd ${projectRoot}/backend
yarn start
Admin
cd ${projectRoot}/admin
yarn start
`)
if (hasStorefront) {
console.log(`
Storefront
cd ${projectRoot}/storefront
yarn start
`)
}
}
@@ -0,0 +1,377 @@
import { execSync } from "child_process"
import execa from "execa"
import { spin } from "tiny-spin"
import { sync as existsSync } from "fs-exists-cached"
import fs from "fs-extra"
import hostedGitInfo from "hosted-git-info"
import isValid from "is-valid-path"
import sysPath from "path"
import url from "url"
import { reporter } from "./reporter"
import { getConfigStore } from "./get-config-store"
const packageManagerConfigKey = `cli.packageManager`
export const getPackageManager = (npmConfigUserAgent) => {
const configStore = getConfigStore()
const actualPackageManager = configStore.get(packageManagerConfigKey)
if (actualPackageManager) {
return actualPackageManager
}
if (npmConfigUserAgent?.includes(`yarn`)) {
configStore.set(packageManagerConfigKey, `yarn`)
return `yarn`
}
configStore.set(packageManagerConfigKey, `npm`)
return `npm`
}
const removeUndefined = (obj) => {
return Object.fromEntries(
Object.entries(obj)
.filter(([_, v]) => v != null)
.map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v])
)
}
const spawnWithArgs = (file, args, options) =>
execa(file, args, { stdio: "ignore", preferLocal: false, ...options })
const spawn = (cmd, options) => {
const [file, ...args] = cmd.split(/\s+/)
return spawnWithArgs(file, args, options)
}
// Checks the existence of yarn package
// We use yarnpkg instead of yarn to avoid conflict with Hadoop yarn
// Refer to https://github.com/yarnpkg/yarn/issues/673
const checkForYarn = () => {
try {
execSync(`yarnpkg --version`, { stdio: `ignore` })
return true
} catch (e) {
return false
}
}
const isAlreadyGitRepository = async () => {
try {
return await spawn(`git rev-parse --is-inside-work-tree`, {
stdio: `pipe`,
}).then((output) => output.stdout === `true`)
} catch (err) {
return false
}
}
// Initialize newly cloned directory as a git repo
const gitInit = async (rootPath) => {
reporter.info(`Initialising git in ${rootPath}`)
return await spawn(`git init`, { cwd: rootPath })
}
// Create a .gitignore file if it is missing in the new directory
const maybeCreateGitIgnore = async (rootPath) => {
if (existsSync(sysPath.join(rootPath, `.gitignore`))) {
return
}
reporter.info(`Creating minimal .gitignore in ${rootPath}`)
await fs.writeFile(
sysPath.join(rootPath, `.gitignore`),
`.cache\nnode_modules\npublic\n`
)
reporter.success(`Created .gitignore in ${rootPath}`)
}
// Create an initial git commit in the new directory
const createInitialGitCommit = async (rootPath, starterUrl) => {
reporter.info(`Create initial git commit in ${rootPath}`)
await spawn(`git add -A`, { cwd: rootPath })
// use execSync instead of spawn to handle git clients using
// pgp signatures (with password)
try {
execSync(`git commit -m "Initial commit from medusa: (${starterUrl})"`, {
cwd: rootPath,
})
} catch {
// Remove git support if initial commit fails
reporter.warn(`Initial git commit failed - removing git support\n`)
fs.removeSync(sysPath.join(rootPath, `.git`))
}
}
// Executes `npm install` or `yarn install` in rootPath.
const install = async (rootPath, verbose) => {
const prevDir = process.cwd()
const stop = spin(`Installing packages...`)
console.log() // Add some space
process.chdir(rootPath)
const npmConfigUserAgent = process.env.npm_config_user_agent
try {
if (getPackageManager() === `yarn` && checkForYarn()) {
await fs.remove(`package-lock.json`)
await spawn(`yarnpkg`, { stdio: verbose ? `inherit` : `ignore` })
} else {
await fs.remove(`yarn.lock`)
await spawn(`npm install`, { stdio: verbose ? `inherit` : `ignore` })
}
} finally {
stop()
console.log()
reporter.success(`Packages installed`)
process.chdir(prevDir)
}
}
const ignored = (path) => !/^\.(git|hg)$/.test(sysPath.basename(path))
// Copy starter from file system.
const copy = async (starterPath, rootPath) => {
// Chmod with 755.
// 493 = parseInt('755', 8)
await fs.ensureDir(rootPath, { mode: 493 })
if (!existsSync(starterPath)) {
throw new Error(`starter ${starterPath} doesn't exist`)
}
if (starterPath === `.`) {
throw new Error(
`You can't create a starter from the existing directory. If you want to
create a new project in the current directory, the trailing dot isn't
necessary. If you want to create a project from a local starter, run
something like "medusa new my-medusa-store ../local-medusa-starter"`
)
}
const stop = spin(`Creating new site from local starter: ${starterPath}`)
reporter.info(`Copying local starter to ${rootPath} ...`)
await fs.copy(starterPath, rootPath, { filter: ignored })
stop()
console.log()
reporter.success(`Created starter directory layout`)
console.log() // Add some space
await install(rootPath)
return true
}
// Clones starter from URI.
const clone = async (hostInfo, rootPath, keepGit, verbose = false) => {
let url
// Let people use private repos accessed over SSH.
if (hostInfo.getDefaultRepresentation() === `sshurl`) {
url = hostInfo.ssh({ noCommittish: true })
// Otherwise default to normal git syntax.
} else {
url = hostInfo.https({ noCommittish: true, noGitPlus: true })
}
const branch = hostInfo.committish ? [`-b`, hostInfo.committish] : []
const stop = spin(`Creating new project from git: ${url}`)
const args = [
`clone`,
...branch,
url,
rootPath,
`--recursive`,
`--depth=1`,
].filter((arg) => Boolean(arg))
await execa(`git`, args, {})
.then(() => {
stop()
console.log()
reporter.success(`Created starter directory layout`)
})
.catch((err) => {
stop()
console.log()
reporter.error(`Failed to clone repository`)
throw err
})
if (!keepGit) {
await fs.remove(sysPath.join(rootPath, `.git`))
}
await install(rootPath, verbose)
const isGit = await isAlreadyGitRepository()
if (!isGit) await gitInit(rootPath)
await maybeCreateGitIgnore(rootPath)
if (!isGit) await createInitialGitCommit(rootPath, url)
}
const getMedusaConfig = (rootPath) => {
try {
const configPath = sysPath.join(rootPath, "medusa-config.js")
if (existsSync(configPath)) {
const resolved = sysPath.resolve(configPath)
const configModule = require(resolved)
return configModule
}
throw Error()
} catch (err) {
return null
}
return {}
}
const getPaths = async (starterPath, rootPath) => {
let selectedOtherStarter = false
// set defaults if no root or starter has been set yet
rootPath = rootPath || process.cwd()
starterPath = starterPath || `medusajs/medusa-starter-default`
return { starterPath, rootPath, selectedOtherStarter }
}
const successMessage = (path) => {
reporter.info(`Your new Medusa project is ready for you! To start developing run:
cd ${path}
medusa develop
`)
}
const setupEnvVars = async (rootPath) => {
const templatePath = sysPath.join(rootPath, ".env.template")
const destination = sysPath.join(rootPath, ".env")
if (existsSync(templatePath)) {
fs.renameSync(templatePath, destination)
}
}
const attemptSeed = async (rootPath) => {
const stop = spin("Seeding database")
const pkgPath = sysPath.resolve(rootPath, "package.json")
if (existsSync(pkgPath)) {
const pkg = require(pkgPath)
if (pkg.scripts && pkg.scripts.seed) {
await setupEnvVars(rootPath)
const proc = execa(getPackageManager(), [`run`, `seed`], {
cwd: rootPath,
})
// Useful for development
proc.stdout.pipe(process.stdout)
await proc
.then(() => {
stop()
console.log()
reporter.success("Seed completed")
})
.catch((err) => {
stop()
console.log()
reporter.error("Failed to complete seed; skipping")
console.error(err)
})
} else {
stop()
console.log()
reporter.error("Starter doesn't provide a seed command; skipping.")
}
} else {
stop()
console.log()
reporter.error("Could not find package.json")
}
}
/**
* Main function that clones or copies the starter.
*/
export const newStarter = async (args) => {
const { starter, root, verbose, seed, keepGit } = args
const { starterPath, rootPath, selectedOtherStarter } = await getPaths(
starter,
root
)
const urlObject = url.parse(rootPath)
if (urlObject.protocol && urlObject.host) {
const isStarterAUrl =
starter && !url.parse(starter).hostname && !url.parse(starter).protocol
if (/medusa-starter/gi.test(rootPath) && isStarterAUrl) {
reporter.panic({
id: `10000`,
context: {
starter,
rootPath,
},
})
return
}
reporter.panic({
id: `10001`,
context: {
rootPath,
},
})
return
}
if (!isValid(rootPath)) {
reporter.panic({
id: `10002`,
context: {
path: sysPath.resolve(rootPath),
},
})
return
}
if (existsSync(sysPath.join(rootPath, `package.json`))) {
reporter.panic({
id: `10003`,
context: {
rootPath,
},
})
return
}
const hostedInfo = hostedGitInfo.fromUrl(starterPath)
if (hostedInfo) {
await clone(hostedInfo, rootPath, keepGit, verbose)
} else {
await copy(starterPath, rootPath, verbose)
}
const medusaConfig = getMedusaConfig(rootPath)
if (medusaConfig) {
let isPostgres = false
if (medusaConfig.projectConfig) {
const databaseType = medusaConfig.projectConfig.database_type
isPostgres = databaseType === "postgres"
}
if (!isPostgres && seed) {
await attemptSeed(rootPath)
}
}
}
@@ -0,0 +1,25 @@
export const panicHandler = (panicData = {}) => {
const { id, context } = panicData
switch (id) {
case "10000":
return {
message: `Looks like you provided a URL as your project name. Try "medusa new my-medusa-store ${context.rootPath}" instead.`,
}
case "10001":
return {
message: `Looks like you provided a URL as your project name. Try "medusa new my-medusa-store ${context.rootPath}" instead.`,
}
case "10002":
return {
message: `Could not create project because ${context.path} is not a valid path.`,
}
case "10003":
return {
message: `Directory ${context.rootPath} is already a Node project.`,
}
default:
return {
message: "Unknown error",
}
}
}
@@ -0,0 +1,18 @@
import c from "ansi-colors"
import { panicHandler } from "./panic-handler"
export const reporter = {
info: (message: string): void => console.log(message),
verbose: (message: string): void => console.log(message),
log: (message: string): void => console.log(message),
success: (message: string): void =>
console.log(c.green(c.symbols.check + ` `) + message),
error: (message: string): void =>
console.error(c.red(c.symbols.cross + ` `) + message),
panic: (panicData: { id: string; context: any }): never => {
const { message } = panicHandler(panicData)
console.error(message)
process.exit(1)
},
warn: (message: string): void => console.warn(message),
}
+47
View File
@@ -0,0 +1,47 @@
import fetch from "node-fetch"
import uuidv4 from "uuid/v4"
import { getConfigStore } from "./get-config-store"
const store = getConfigStore()
const medusaCliVersion = require(`../package.json`).version
const analyticsApi =
process.env.MEDUSA_TELEMETRY_API ||
`https://telemetry.medusa-commerce.com/batch`
const getMachineId = (): string => {
let machineId = store.get(`telemetry.machine_id`)
if (typeof machineId !== `string`) {
machineId = uuidv4()
store.set(`telemetry.machine_id`, machineId)
}
return machineId
}
const sessionId = uuidv4()
export const track = (eventType: string, args?: any): void => {
fetch(analyticsApi, {
method: `POST`,
headers: {
"content-type": `application/json`,
"user-agent": `create-medusa-app:${medusaCliVersion}`,
},
body: JSON.stringify({
timestamp: new Date(),
batch: [
{
type: eventType,
timestamp: new Date(),
sessionId,
machine_id: getMachineId(),
component_id: `create-medusa-app`,
cli_version: medusaCliVersion,
properties: args,
},
],
}),
}).catch(() => {}) /* do nothing, it's telemetry */
}
+4
View File
@@ -0,0 +1,4 @@
declare module "stream-filter"
declare module "ansi-wordwrap"
declare module "uuid/v4"
declare module "node-fetch"
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs",
"allowJs": true,
"declaration": false,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.json", "src/**/*.js"],
"exclude": ["**/__tests__/**/*"]
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.18](https://github.com/medusajs/medusa/compare/@medusajs/medusa-cli@1.1.17...@medusajs/medusa-cli@1.1.18) (2021-09-15)
**Note:** Version bump only for package @medusajs/medusa-cli
## [1.1.17](https://github.com/medusajs/medusa/compare/@medusajs/medusa-cli@1.1.16...@medusajs/medusa-cli@1.1.17) (2021-09-14)
**Note:** Version bump only for package @medusajs/medusa-cli
## [1.1.16](https://github.com/medusajs/medusa/compare/@medusajs/medusa-cli@1.1.15...@medusajs/medusa-cli@1.1.16) (2021-08-17)
**Note:** Version bump only for package @medusajs/medusa-cli
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@medusajs/medusa-cli",
"version": "1.1.16",
"version": "1.1.18",
"description": "Command Line interface for Medusa Commerce",
"main": "dist/index.js",
"bin": {
@@ -52,7 +52,7 @@
"joi-objectid": "^3.0.1",
"meant": "^1.0.1",
"medusa-core-utils": "^0.1.27",
"medusa-telemetry": "^0.0.3",
"medusa-telemetry": "^0.0.5",
"netrc-parser": "^3.1.6",
"open": "^8.0.6",
"ora": "^5.4.1",
@@ -66,5 +66,5 @@
"winston": "^3.3.3",
"yargs": "^15.3.1"
},
"gitHead": "cfe19f7f9d3bb17425348362b148a0b4b7a649ef"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
+117 -14
View File
@@ -1300,6 +1300,13 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-align@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==
dependencies:
string-width "^3.0.0"
ansi-escapes@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
@@ -1466,12 +1473,19 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
axios-retry@^3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.1.9.tgz#6c30fc9aeb4519aebaec758b90ef56fa03fe72e8"
integrity sha512-NFCoNIHq8lYkJa6ku4m+V1837TP6lCa7n79Iuf8/AqATAHYB0ISaAS1eyIenDOfHOLtym34W65Sjke2xjg2fsA==
dependencies:
follow-redirects "^1.10.0"
is-retry-allowed "^1.1.0"
axios@^0.21.1:
version "0.21.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
follow-redirects "^1.14.0"
babel-jest@^25.5.1:
version "25.5.1"
@@ -1589,6 +1603,20 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
boxen@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.1.tgz#4faca6a437885add0bf8d99082e272d480814cd4"
integrity sha512-JtIQYts08AFAYGF4eSh3pUt3NQkYV/e75pRtQmAVTLNWR/1L7Bsswxlgzgk8nmLEM+gFszsIlA9BgD3XnSqp3g==
dependencies:
ansi-align "^3.0.0"
camelcase "^6.2.0"
chalk "^4.1.0"
cli-boxes "^2.2.1"
string-width "^4.2.2"
type-fest "^0.20.2"
widest-line "^3.1.0"
wrap-ansi "^7.0.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -1693,6 +1721,11 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
caniuse-lite@^1.0.30001219:
version "1.0.30001236"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz#0a80de4cdf62e1770bb46a30d884fc8d633e3958"
@@ -1780,6 +1813,11 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
ci-info@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6"
integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -1797,6 +1835,11 @@ clean-stack@^3.0.0:
dependencies:
escape-string-regexp "4.0.0"
cli-boxes@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@@ -2203,6 +2246,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
domexception@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -2660,10 +2708,10 @@ fn.name@1.x.x:
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
for-in@^1.0.2:
version "1.0.2"
@@ -2827,6 +2875,14 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
global@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
dependencies:
min-document "^2.19.0"
process "^0.11.10"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -3164,7 +3220,7 @@ is-docker@^2.0.0:
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
is-docker@^2.1.1:
is-docker@^2.1.1, is-docker@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
@@ -3263,6 +3319,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-retry-allowed@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -4043,6 +4104,21 @@ medusa-core-utils@^0.1.27:
"@hapi/joi" "^16.1.8"
joi-objectid "^3.0.1"
medusa-telemetry@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.5.tgz#d7d08fca5cbecc0e853b4e0406194a92c5206caa"
integrity sha512-h7hP5Lc33OkFhMcvfrPcwINzMOuPoG8Vn8O6niKGFxF9RmmQnJgaAG1J43/Eq9ZWBrWi0n42XBttibKwCMViHw==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
boxen "^5.0.1"
ci-info "^3.2.0"
configstore "5.0.1"
global "^4.4.0"
is-docker "^2.2.1"
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -4105,6 +4181,13 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
dependencies:
dom-walk "^0.1.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -4644,6 +4727,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -4837,6 +4925,11 @@ remove-trailing-separator@^1.0.1:
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
remove-trailing-slash@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==
repeat-element@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
@@ -5324,7 +5417,7 @@ string-width@^3.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.0.0:
string-width@^4.0.0, string-width@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
@@ -5494,9 +5587,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"
@@ -5608,6 +5701,11 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
type-fest@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
@@ -5733,6 +5831,11 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.22](https://github.com/medusajs/medusa/compare/medusa-core-utils@1.1.21...medusa-core-utils@1.1.22) (2021-09-15)
**Note:** Version bump only for package medusa-core-utils
## [1.1.21](https://github.com/medusajs/medusa/compare/medusa-core-utils@1.1.20...medusa-core-utils@1.1.21) (2021-09-14)
**Note:** Version bump only for package medusa-core-utils
## [1.1.20](https://github.com/medusajs/medusa/compare/medusa-core-utils@1.1.19...medusa-core-utils@1.1.20) (2021-08-05)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-core-utils",
"version": "1.1.20",
"version": "1.1.22",
"description": "Core utils for Medusa",
"main": "dist/index.js",
"repository": {
@@ -33,5 +33,5 @@
"joi": "^17.3.0",
"joi-objectid": "^3.0.1"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
+3 -3
View File
@@ -4938,9 +4938,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.20](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.19...medusa-dev-cli@0.0.20) (2021-09-15)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.19](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.18...medusa-dev-cli@0.0.19) (2021-09-14)
**Note:** Version bump only for package medusa-dev-cli
## [0.0.18](https://github.com/medusajs/medusa/compare/medusa-dev-cli@0.0.17...medusa-dev-cli@0.0.18) (2021-07-26)
**Note:** Version bump only for package medusa-dev-cli
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "medusa-dev-cli",
"description": "CLI helpers for contributors working on Medusa",
"version": "0.0.18",
"version": "0.0.20",
"author": "Sebastian Rindom <skrindom@gmail.com>",
"bin": {
"medusa-dev": "./dist/index.js"
@@ -24,7 +24,7 @@
"devDependencies": {
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"babel-preset-medusa-package": "^1.1.13",
"babel-preset-medusa-package": "^1.1.15",
"cross-env": "^7.0.3"
},
"homepage": "https://github.com/medusajs/medusa/tree/master/packages/medusa-dev-cli#readme",
@@ -47,5 +47,5 @@
"engines": {
"node": ">=12.13.0"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
+13
View File
@@ -0,0 +1,13 @@
{
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-instanceof",
"@babel/plugin-transform-classes"
],
"presets": ["@babel/preset-env"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
+16
View File
@@ -0,0 +1,16 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
/__mocks__
+9
View File
@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
+7
View File
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
+1
View File
@@ -0,0 +1 @@
// noop
+46
View File
@@ -0,0 +1,46 @@
{
"name": "medusa-file-s3",
"version": "1.0.0",
"description": "AWS s3 file connector for Medusa",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-file-s3"
},
"author": "Sebastian Mateos Nicolajsen",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/node": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.9.6",
"client-sessions": "^0.8.0",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^0.3.0"
},
"scripts": {
"build": "babel src -d .",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"peerDependencies": {
"medusa-interfaces": "1.x"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.15.4",
"aws-sdk": "^2.983.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20",
"medusa-test-utils": "^1.1.23"
}
}
@@ -0,0 +1,73 @@
import fs from "fs"
import aws from "aws-sdk"
import { FileService } from "medusa-interfaces"
class S3Service extends FileService {
constructor({}, options) {
super()
this.bucket_ = options.bucket
this.s3Url_ = options.s3_url
this.accessKeyId_ = options.access_key_id
this.secretAccessKey_ = options.secret_access_key
this.region_ = options.region
this.endpoint_ = options.endpoint
}
upload(file) {
aws.config.setPromisesDependency()
aws.config.update({
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
region: this.region_,
endpoint: this.endpoint_,
})
const s3 = new aws.S3()
var params = {
ACL: "public-read",
Bucket: this.bucket_,
Body: fs.createReadStream(file.path),
Key: `${file.originalname}`,
}
return new Promise((resolve, reject) => {
s3.upload(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve({ url: data.Location })
})
})
}
delete(file) {
aws.config.setPromisesDependency()
aws.config.update({
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
region: this.region_,
endpoint: this.endpoint_,
})
const s3 = new aws.S3()
var params = {
Bucket: this.bucket_,
Key: `${file}`,
}
return new Promise((resolve, reject) => {
s3.deleteObject(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
})
})
}
}
export default S3Service
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-file-spaces@1.1.24...medusa-file-spaces@1.1.25) (2021-09-15)
**Note:** Version bump only for package medusa-file-spaces
## [1.1.24](https://github.com/medusajs/medusa/compare/medusa-file-spaces@1.1.23...medusa-file-spaces@1.1.24) (2021-09-14)
**Note:** Version bump only for package medusa-file-spaces
## [1.1.23](https://github.com/medusajs/medusa/compare/medusa-file-spaces@1.1.22...medusa-file-spaces@1.1.23) (2021-08-05)
**Note:** Version bump only for package medusa-file-spaces
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-file-spaces",
"version": "1.1.23",
"version": "1.1.25",
"description": "Digital Ocean Spaces file connector for Medusa",
"main": "index.js",
"repository": {
@@ -40,9 +40,9 @@
"aws-sdk": "^2.710.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20",
"medusa-test-utils": "^1.1.23",
"medusa-core-utils": "^1.1.22",
"medusa-test-utils": "^1.1.25",
"stripe": "^8.50.0"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.22](https://github.com/medusajs/medusa/compare/medusa-fulfillment-manual@1.1.21...medusa-fulfillment-manual@1.1.22) (2021-09-15)
**Note:** Version bump only for package medusa-fulfillment-manual
## [1.1.21](https://github.com/medusajs/medusa/compare/medusa-fulfillment-manual@1.1.20...medusa-fulfillment-manual@1.1.21) (2021-09-14)
**Note:** Version bump only for package medusa-fulfillment-manual
## [1.1.20](https://github.com/medusajs/medusa/compare/medusa-fulfillment-manual@1.1.19...medusa-fulfillment-manual@1.1.20) (2021-08-05)
**Note:** Version bump only for package medusa-fulfillment-manual
@@ -1,6 +1,6 @@
{
"name": "medusa-fulfillment-manual",
"version": "1.1.20",
"version": "1.1.22",
"description": "A manual fulfillment provider for Medusa",
"main": "index.js",
"repository": {
@@ -36,7 +36,7 @@
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/runtime": "^7.7.6",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20"
"medusa-core-utils": "^1.1.22"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.26](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.25...medusa-fulfillment-webshipper@1.1.26) (2021-09-15)
**Note:** Version bump only for package medusa-fulfillment-webshipper
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.24...medusa-fulfillment-webshipper@1.1.25) (2021-09-14)
### Features
* update and cancel swaps, claims, and returns ([#310](https://github.com/medusajs/medusa/issues/310)) ([cf66f97](https://github.com/medusajs/medusa/commit/cf66f97758003a41737602d4b1b1051b266d4f81))
## [1.1.24](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.23...medusa-fulfillment-webshipper@1.1.24) (2021-08-05)
**Note:** Version bump only for package medusa-fulfillment-webshipper
@@ -1,6 +1,6 @@
{
"name": "medusa-fulfillment-webshipper",
"version": "1.1.24",
"version": "1.1.26",
"description": "Webshipper Fulfillment provider for Medusa",
"main": "index.js",
"repository": {
@@ -37,7 +37,7 @@
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20"
"medusa-core-utils": "^1.1.22"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -582,7 +582,10 @@ class WebshipperFulfillmentService extends FulfillmentService {
}
if (order) {
if (order.data.attributes.status !== "pending") {
if (
order.data.attributes.status !== "pending" &&
order.data.attributes.status !== "missing_rate"
) {
if (order.data.attributes.status === "cancelled") {
return Promise.resolve(order)
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.23](https://github.com/medusajs/medusa/compare/medusa-interfaces@1.1.22...medusa-interfaces@1.1.23) (2021-09-15)
**Note:** Version bump only for package medusa-interfaces
## [1.1.22](https://github.com/medusajs/medusa/compare/medusa-interfaces@1.1.21...medusa-interfaces@1.1.22) (2021-09-14)
**Note:** Version bump only for package medusa-interfaces
## [1.1.21](https://github.com/medusajs/medusa/compare/medusa-interfaces@1.1.20...medusa-interfaces@1.1.21) (2021-08-05)
**Note:** Version bump only for package medusa-interfaces
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-interfaces",
"version": "1.1.21",
"version": "1.1.23",
"description": "Core interfaces for Medusa",
"main": "dist/index.js",
"repository": {
@@ -35,7 +35,7 @@
"typeorm": "0.x"
},
"dependencies": {
"medusa-core-utils": "^1.1.20"
"medusa-core-utils": "^1.1.22"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.27](https://github.com/medusajs/medusa/compare/medusa-payment-adyen@1.1.26...medusa-payment-adyen@1.1.27) (2021-09-15)
**Note:** Version bump only for package medusa-payment-adyen
## [1.1.26](https://github.com/medusajs/medusa/compare/medusa-payment-adyen@1.1.25...medusa-payment-adyen@1.1.26) (2021-09-14)
**Note:** Version bump only for package medusa-payment-adyen
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-payment-adyen@1.1.24...medusa-payment-adyen@1.1.25) (2021-08-05)
**Note:** Version bump only for package medusa-payment-adyen
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-adyen",
"version": "1.1.25",
"version": "1.1.27",
"description": "Adyen Payment provider for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -24,7 +24,7 @@
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.23"
"medusa-test-utils": "^1.1.25"
},
"scripts": {
"build": "babel src -d .",
@@ -42,7 +42,7 @@
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20"
"medusa-core-utils": "^1.1.22"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.27](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.26...medusa-payment-klarna@1.1.27) (2021-09-15)
**Note:** Version bump only for package medusa-payment-klarna
## [1.1.26](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.25...medusa-payment-klarna@1.1.26) (2021-09-14)
**Note:** Version bump only for package medusa-payment-klarna
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.24...medusa-payment-klarna@1.1.25) (2021-08-05)
**Note:** Version bump only for package medusa-payment-klarna
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-klarna",
"version": "1.1.25",
"version": "1.1.27",
"description": "Klarna Payment provider for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -40,8 +40,8 @@
"axios": "^0.21.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20",
"medusa-test-utils": "^1.1.23"
"medusa-core-utils": "^1.1.22",
"medusa-test-utils": "^1.1.25"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.4](https://github.com/medusajs/medusa/compare/medusa-payment-manual@1.0.4...medusa-payment-manual@1.0.4) (2021-09-15)
**Note:** Version bump only for package medusa-payment-manual
## [1.0.3](https://github.com/medusajs/medusa/compare/medusa-payment-manual@1.0.4...medusa-payment-manual@1.0.3) (2021-09-14)
**Note:** Version bump only for package medusa-payment-manual
## [1.0.4](https://github.com/medusajs/medusa/compare/medusa-payment-manual@1.0.2...medusa-payment-manual@1.0.4) (2021-09-02)
### Features
* Update to API references look and feel ([#343](https://github.com/medusajs/medusa/issues/343)) ([143f06a](https://github.com/medusajs/medusa/commit/143f06aa397dcc16991405a6143c22eaa0e3ffd9))
## [1.0.3](https://github.com/medusajs/medusa/compare/medusa-payment-manual@1.0.2...medusa-payment-manual@1.0.3) (2021-08-31)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-manual",
"version": "1.0.3",
"version": "1.0.4",
"description": "A dummy payment provider to be used for testing or manual payments",
"main": "index.js",
"repository": {
@@ -25,7 +25,7 @@
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.23"
"medusa-test-utils": "^1.1.25"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__",
@@ -36,5 +36,5 @@
"peerDependencies": {
"medusa-interfaces": "1.x"
},
"gitHead": "cb44b73a4ec0319c77058816022367183f28857c"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.26](https://github.com/medusajs/medusa/compare/medusa-payment-paypal@1.0.25...medusa-payment-paypal@1.0.26) (2021-09-15)
**Note:** Version bump only for package medusa-payment-paypal
## [1.0.25](https://github.com/medusajs/medusa/compare/medusa-payment-paypal@1.0.24...medusa-payment-paypal@1.0.25) (2021-09-14)
**Note:** Version bump only for package medusa-payment-paypal
## [1.0.24](https://github.com/medusajs/medusa/compare/medusa-payment-paypal@1.0.23...medusa-payment-paypal@1.0.24) (2021-08-05)
**Note:** Version bump only for package medusa-payment-paypal
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-paypal",
"version": "1.0.24",
"version": "1.0.26",
"description": "Paypal Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {
@@ -26,8 +26,8 @@
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-interfaces": "^1.1.21",
"medusa-test-utils": "^1.1.23"
"medusa-interfaces": "^1.1.23",
"medusa-test-utils": "^1.1.25"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__,**/__mocks__",
@@ -42,7 +42,7 @@
"@paypal/checkout-server-sdk": "^1.0.2",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20"
"medusa-core-utils": "^1.1.22"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.26](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.25...medusa-payment-stripe@1.1.26) (2021-09-22)
**Note:** Version bump only for package medusa-payment-stripe
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.24...medusa-payment-stripe@1.1.25) (2021-09-15)
**Note:** Version bump only for package medusa-payment-stripe
## [1.1.24](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.23...medusa-payment-stripe@1.1.24) (2021-09-14)
**Note:** Version bump only for package medusa-payment-stripe
## [1.1.23](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.22...medusa-payment-stripe@1.1.23) (2021-08-05)
**Note:** Version bump only for package medusa-payment-stripe
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-stripe",
"version": "1.1.23",
"version": "1.1.26",
"description": "Stripe Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {
@@ -26,7 +26,7 @@
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.23"
"medusa-test-utils": "^1.1.25"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__",
@@ -40,8 +40,8 @@
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20",
"medusa-core-utils": "^1.1.22",
"stripe": "^8.50.0"
},
"gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f"
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
@@ -0,0 +1,241 @@
import _ from "lodash"
import Stripe from "stripe"
import { PaymentService } from "medusa-interfaces"
class IdealProviderService extends PaymentService {
static identifier = "stripe-ideal"
constructor(
{ stripeProviderService, customerService, totalsService, regionService },
options
) {
super()
/**
* Required Stripe options:
* {
* api_key: "stripe_secret_key", REQUIRED
* webhook_secret: "stripe_webhook_secret", REQUIRED
* // Use this flag to capture payment immediately (default is false)
* capture: true
* }
*/
this.options_ = options
/** @private @const {Stripe} */
this.stripe_ = Stripe(options.api_key)
/** @private @const {CustomerService} */
this.stripeProviderService_ = stripeProviderService
/** @private @const {CustomerService} */
this.customerService_ = customerService
/** @private @const {RegionService} */
this.regionService_ = regionService
/** @private @const {TotalsService} */
this.totalsService_ = totalsService
}
/**
* Fetches Stripe payment intent. Check its status and returns the
* corresponding Medusa status.
* @param {object} paymentData - payment method data from cart
* @returns {string} the status of the payment intent
*/
async getStatus(paymentData) {
return await this.stripeProviderService_.getStatus(paymentData)
}
/**
* Fetches a customers saved payment methods if registered in Stripe.
* @param {object} customer - customer to fetch saved cards for
* @returns {Promise<Array<object>>} saved payments methods
*/
async retrieveSavedMethods(customer) {
return Promise.resolve([])
}
/**
* Fetches a Stripe customer
* @param {string} customerId - Stripe customer id
* @returns {Promise<object>} Stripe customer
*/
async retrieveCustomer(customerId) {
return await this.stripeProviderService_.retrieveCustomer(customerId)
}
/**
* Creates a Stripe customer using a Medusa customer.
* @param {object} customer - Customer data from Medusa
* @returns {Promise<object>} Stripe customer
*/
async createCustomer(customer) {
return await this.stripeProviderService_.createCustomer(customer)
}
/**
* Creates a Stripe payment intent.
* If customer is not registered in Stripe, we do so.
* @param {object} cart - cart to create a payment for
* @returns {object} Stripe payment intent
*/
async createPayment(cart) {
const { customer_id, region_id, email } = cart
const region = await this.regionService_.retrieve(region_id)
const { currency_code } = region
const amount = await this.totalsService_.getTotal(cart)
const intentRequest = {
amount: Math.round(amount),
currency: currency_code,
payment_method_types: ["ideal"],
capture_method: "automatic",
metadata: { cart_id: `${cart.id}` },
}
if (customer_id) {
const customer = await this.customerService_.retrieve(customer_id)
if (customer.metadata?.stripe_id) {
intentRequest.customer = customer.metadata.stripe_id
} else {
const stripeCustomer = await this.createCustomer({
email,
id: customer_id,
})
intentRequest.customer = stripeCustomer.id
}
} else {
const stripeCustomer = await this.createCustomer({
email,
})
intentRequest.customer = stripeCustomer.id
}
const paymentIntent = await this.stripe_.paymentIntents.create(
intentRequest
)
return paymentIntent
}
/**
* Retrieves Stripe payment intent.
* @param {object} data - the data of the payment to retrieve
* @returns {Promise<object>} Stripe payment intent
*/
async retrievePayment(data) {
return await this.stripeProviderService_.retrievePayment(data)
}
/**
* Gets a Stripe payment intent and returns it.
* @param {object} sessionData - the data of the payment to retrieve
* @returns {Promise<object>} Stripe payment intent
*/
async getPaymentData(sessionData) {
return await this.stripeProviderService_.getPaymentData(sessionData)
}
/**
* Authorizes Stripe payment intent by simply returning
* the status for the payment intent in use.
* @param {object} sessionData - payment session data
* @param {object} context - properties relevant to current context
* @returns {Promise<{ status: string, data: object }>} result with data and status
*/
async authorizePayment(sessionData, context = {}) {
return await this.stripeProviderService_.authorizePayment(
sessionData,
context
)
}
async updatePaymentData(sessionData, update) {
return await this.stripeProviderService_.updatePaymentData(
sessionData,
update
)
}
/**
* Updates Stripe payment intent.
* @param {object} sessionData - payment session data.
* @param {object} update - objec to update intent with
* @returns {object} Stripe payment intent
*/
async updatePayment(sessionData, cart) {
try {
const stripeId = cart.customer?.metadata?.stripe_id || undefined
if (stripeId !== sessionData.customer) {
return this.createPayment(cart)
} else {
if (cart.total && sessionData.amount === Math.round(cart.total)) {
return sessionData
}
return this.stripe_.paymentIntents.update(sessionData.id, {
amount: Math.round(cart.total),
})
}
} catch (error) {
throw error
}
}
async deletePayment(payment) {
return await this.stripeProviderService_.deletePayment(payment)
}
/**
* Updates customer of Stripe payment intent.
* @param {string} paymentIntentId - id of payment intent to update
* @param {string} customerId - id of new Stripe customer
* @returns {object} Stripe payment intent
*/
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
return await this.stripeProviderService_.updatePaymentIntentCustomer(
paymentIntentId,
customerId
)
}
/**
* Captures payment for Stripe payment intent.
* @param {object} paymentData - payment method data from cart
* @returns {object} Stripe payment intent
*/
async capturePayment(payment) {
return await this.stripeProviderService_.capturePayment(payment)
}
/**
* Refunds payment for Stripe payment intent.
* @param {object} paymentData - payment method data from cart
* @param {number} amountToRefund - amount to refund
* @returns {string} refunded payment intent
*/
async refundPayment(payment, amountToRefund) {
return await this.stripeProviderService_.refundPayment(
payment,
amountToRefund
)
}
/**
* Cancels payment for Stripe payment intent.
* @param {object} paymentData - payment method data from cart
* @returns {object} canceled payment intent
*/
async cancelPayment(payment) {
return await this.stripeProviderService_.cancelPayment(payment)
}
}
export default IdealProviderService
Loaded 100 of 271 files, more files were not shown because too many files have changed in this diff. Show more