fix: merge conflicts

This commit is contained in:
zakariaelas
2021-09-30 20:29:58 +01:00
134 changed files with 5517 additions and 628 deletions

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()

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
},
};
}

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.

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).

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.

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). -->

View File

@@ -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

View File

@@ -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) {
@@ -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. -->

View File

@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/discounts creates admin session correctly 1`] = `
Object {
"api_token": "test_token",
"created_at": Any<String>,
"deleted_at": null,
"email": "admin@medusa.js",
"first_name": null,
"id": "admin_user",
"last_name": null,
"metadata": null,
"updated_at": Any<String>,
}
`;

View File

@@ -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,
},
]
`;

View File

@@ -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",
}
`;

View File

@@ -0,0 +1,54 @@
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 { exportAllDeclaration } = require("@babel/types")
jest.setTimeout(30000)
describe("/admin/auth", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
try {
await adminSeeder(dbConnection)
} catch (e) {
throw e
}
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
it("creates admin session correctly", async () => {
const api = useApi()
const response = await api
.post("/admin/auth", {
email: "admin@medusa.js",
password: "secret_password",
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.user.password_hash).toEqual(undefined)
expect(response.data.user).toMatchSnapshot({
email: "admin@medusa.js",
created_at: expect.any(String),
updated_at: expect.any(String),
})
})
})

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",
})
)
})
})
})

View File

@@ -24,6 +24,67 @@ describe("/admin/discounts", () => {
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 {
@@ -97,6 +158,193 @@ describe("/admin/discounts", () => {
})
)
})
it("creates a discount with start and end dates", async () => {
const api = useApi()
const response = await api
.post(
"/admin/discounts",
{
code: "HELLOWORLD",
rule: {
description: "test",
type: "percentage",
value: 10,
allocation: "total",
},
usage_limit: 10,
starts_at: new Date("09/15/2021 11:50"),
ends_at: new Date("09/15/2021 17:50"),
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 10,
starts_at: expect.any(String),
ends_at: expect.any(String),
})
)
expect(new Date(response.data.discount.starts_at)).toEqual(
new Date("09/15/2021 11:50")
)
expect(new Date(response.data.discount.ends_at)).toEqual(
new Date("09/15/2021 17:50")
)
const updated = await api
.post(
`/admin/discounts/${response.data.discount.id}`,
{
usage_limit: 20,
starts_at: new Date("09/14/2021 11:50"),
ends_at: new Date("09/17/2021 17:50"),
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(updated.status).toEqual(200)
expect(updated.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 20,
starts_at: expect.any(String),
ends_at: expect.any(String),
})
)
expect(new Date(updated.data.discount.starts_at)).toEqual(
new Date("09/14/2021 11:50")
)
expect(new Date(updated.data.discount.ends_at)).toEqual(
new Date("09/17/2021 17:50")
)
})
it("fails to update end date to a date before start date", async () => {
expect.assertions(6)
const api = useApi()
const response = await api
.post(
"/admin/discounts",
{
code: "HELLOWORLD",
rule: {
description: "test",
type: "percentage",
value: 10,
allocation: "total",
},
usage_limit: 10,
starts_at: new Date("09/15/2021 11:50"),
ends_at: new Date("09/15/2021 17:50"),
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 10,
starts_at: expect.any(String),
ends_at: expect.any(String),
})
)
expect(new Date(response.data.discount.starts_at)).toEqual(
new Date("09/15/2021 11:50")
)
expect(new Date(response.data.discount.ends_at)).toEqual(
new Date("09/15/2021 17:50")
)
await api
.post(
`/admin/discounts/${response.data.discount.id}`,
{
usage_limit: 20,
ends_at: new Date("09/11/2021 17:50"),
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
expect(err.response.status).toEqual(400)
expect(err.response.data.message).toEqual(
`"ends_at" must be greater than "starts_at"`
)
})
})
it("fails to create discount with end date before start date", async () => {
expect.assertions(2)
const api = useApi()
const response = await api
.post(
"/admin/discounts",
{
code: "HELLOWORLD",
rule: {
description: "test",
type: "percentage",
value: 10,
allocation: "total",
},
usage_limit: 10,
starts_at: new Date("09/15/2021 11:50"),
ends_at: new Date("09/14/2021 17:50"),
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
expect(err.response.status).toEqual(400)
expect(err.response.data.message).toEqual([
expect.objectContaining({
message: `"ends_at" must be greater than "ref:starts_at"`,
}),
])
})
})
})
describe("testing for soft-deletion + uniqueness on discount codes", () => {
@@ -225,6 +473,21 @@ describe("/admin/discounts", () => {
is_dynamic: true,
is_disabled: false,
rule_id: "test-discount-rule",
valid_duration: "P2Y",
})
await manager.insert(DiscountRule, {
id: "test-discount-rule1",
description: "Dynamic rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount1",
code: "DYNAMICCode",
is_dynamic: true,
is_disabled: false,
rule_id: "test-discount-rule1",
})
} catch (err) {
console.log(err)
@@ -237,7 +500,7 @@ describe("/admin/discounts", () => {
await db.teardown()
})
it("creates a dynamic discount", async () => {
it("creates a dynamic discount with ends_at", async () => {
const api = useApi()
const response = await api
@@ -257,6 +520,40 @@ describe("/admin/discounts", () => {
})
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
ends_at: expect.any(String),
})
)
})
it("creates a dynamic discount without ends_at", async () => {
const api = useApi()
const response = await api
.post(
"/admin/discounts/test-discount1/dynamic-codes",
{
code: "HELLOWORLD",
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
// console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
ends_at: null,
})
)
})
})
})

View File

@@ -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")
})
})
})

View File

@@ -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)
})
})
})

View File

@@ -1013,6 +1013,47 @@ describe("/admin/orders", () => {
])
})
it("list all orders with matching order email", async () => {
const api = useApi()
const response = await api.get(
"/admin/orders?fields=id,email&q=test@email",
{
headers: {
authorization: "Bearer test_token",
},
}
)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.orders).toEqual([
expect.objectContaining({
id: "test-order",
email: "test@email.com",
}),
])
})
it("list all orders with matching shipping_address first name", async () => {
const api = useApi()
const response = await api.get("/admin/orders?q=lebron", {
headers: {
authorization: "Bearer test_token",
},
})
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.orders).toEqual([
expect.objectContaining({
id: "test-order",
shipping_address: expect.objectContaining({ first_name: "lebron" }),
}),
])
})
it("successfully lists orders with greater than", async () => {
const api = useApi()

View File

@@ -42,6 +42,73 @@ describe("/admin/products", () => {
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()
@@ -297,6 +364,7 @@ describe("/admin/products", () => {
discountable: true,
is_giftcard: false,
handle: "test",
status: "draft",
images: expect.arrayContaining([
expect.objectContaining({
url: "test-image.png",
@@ -455,7 +523,7 @@ describe("/admin/products", () => {
)
})
it("updates a product (update prices, tags, delete collection, delete type, replaces images)", async () => {
it("updates a product (update prices, tags, update status, delete collection, delete type, replaces images)", async () => {
const api = useApi()
const payload = {
@@ -476,6 +544,7 @@ describe("/admin/products", () => {
tags: [{ value: "123" }],
images: ["test-image-2.png"],
type: { value: "test-type-2" },
status: "published",
}
const response = await api
@@ -514,6 +583,7 @@ describe("/admin/products", () => {
}),
],
type: null,
status: "published",
collection: null,
type: expect.objectContaining({
value: "test-type-2",
@@ -522,6 +592,25 @@ describe("/admin/products", () => {
)
})
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()

View File

@@ -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")
})
})
})
})

View File

@@ -1,102 +1,53 @@
const path = require("path");
const path = require("path")
const {
Region,
ShippingProfile,
ShippingOption,
ShippingOptionRequirement,
} = require("@medusajs/medusa");
} = 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")
const shippingOptionSeeder = require("../../helpers/shipping-option-seeder")
jest.setTimeout(30000);
jest.setTimeout(30000)
describe("/admin/shipping-options", () => {
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("POST /admin/shipping-options", () => {
describe("POST /admin/shipping-options/:id", () => {
beforeEach(async () => {
const manager = dbConnection.manager;
try {
await adminSeeder(dbConnection);
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,
});
await adminSeeder(dbConnection)
await shippingOptionSeeder(dbConnection)
} catch (err) {
console.error(err);
throw err;
console.error(err)
throw err
}
});
})
afterEach(async () => {
const db = useDb();
await db.teardown();
});
const db = useDb()
await db.teardown()
})
it("updates a shipping option with no existing requirements", async () => {
const api = useApi();
const api = useApi()
const payload = {
name: "Test option",
@@ -111,36 +62,36 @@ describe("/admin/shipping-options", () => {
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;
const requirements = res.data.shipping_option.requirements
expect(res.status).toEqual(200);
expect(requirements.length).toEqual(2);
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 api = useApi()
const payload = {
name: "Test option",
@@ -157,7 +108,7 @@ describe("/admin/shipping-options", () => {
amount: 2,
},
],
};
}
const res = await api
.post(`/admin/shipping-options/test-out`, payload, {
@@ -166,15 +117,15 @@ describe("/admin/shipping-options", () => {
},
})
.catch((err) => {
return err.response;
});
return err.response
})
expect(res.status).toEqual(400);
expect(res.data.message).toEqual("ID does not exist");
});
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 api = useApi()
const payload = {
requirements: [
@@ -190,7 +141,7 @@ describe("/admin/shipping-options", () => {
},
],
amount: 200,
};
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
@@ -199,14 +150,14 @@ describe("/admin/shipping-options", () => {
},
})
.catch((err) => {
console.log(err.response.data.message);
});
console.log(err.response.data.message)
})
expect(res.status).toEqual(200);
});
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 api = useApi()
const payload = {
requirements: [
@@ -216,7 +167,7 @@ describe("/admin/shipping-options", () => {
amount: 15,
},
],
};
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
@@ -225,14 +176,14 @@ describe("/admin/shipping-options", () => {
},
})
.catch((err) => {
console.log(err.response.data.message);
});
console.log(err.response.data.message)
})
expect(res.status).toEqual(200);
});
expect(res.status).toEqual(200)
})
it("succesfully updates a set of requirements because max. subtotal >= min. subtotal", async () => {
const api = useApi();
const api = useApi()
const payload = {
requirements: [
@@ -247,7 +198,7 @@ describe("/admin/shipping-options", () => {
amount: 200,
},
],
};
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
@@ -256,16 +207,16 @@ describe("/admin/shipping-options", () => {
},
})
.catch((err) => {
console.log(err.response.data.message);
});
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);
});
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 api = useApi()
const payload = {
requirements: [
@@ -280,7 +231,7 @@ describe("/admin/shipping-options", () => {
amount: 200,
},
],
};
}
const res = await api
.post(`/admin/shipping-options/test-option-req`, payload, {
@@ -289,13 +240,147 @@ describe("/admin/shipping-options", () => {
},
})
.catch((err) => {
return err.response;
});
return err.response
})
expect(res.status).toEqual(400);
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"
)
}
})
})
})

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",
})
);
});
});
});
)
})
})
})

View File

@@ -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",
}),
])
)
})
})
})

View File

@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/discounts creates store session correctly 1`] = `
Object {
"billing_address_id": null,
"created_at": Any<String>,
"deleted_at": null,
"email": "test@testesen.dk",
"first_name": "test",
"has_account": true,
"id": Any<String>,
"last_name": "testesen",
"metadata": null,
"orders": Array [],
"phone": "12345678",
"updated_at": Any<String>,
}
`;

View File

@@ -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",

View File

@@ -101,6 +101,7 @@ Object {
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Array [
Object {

View File

@@ -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",

View File

@@ -0,0 +1,64 @@
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 { exportAllDeclaration } = require("@babel/types")
jest.setTimeout(30000)
describe("/admin/auth", () => {
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()
})
it("creates store session correctly", async () => {
const api = useApi()
await api
.post("/store/customers", {
email: "test@testesen.dk",
password: "secret_password",
first_name: "test",
last_name: "testesen",
phone: "12345678",
})
.catch((err) => {
console.log(err)
})
const response = await api
.post("/store/auth", {
email: "test@testesen.dk",
password: "secret_password",
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.customer.password_hash).toEqual(undefined)
expect(response.data.customer).toMatchSnapshot({
id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
first_name: "test",
last_name: "testesen",
phone: "12345678",
email: "test@testesen.dk",
})
})
})

View File

@@ -27,8 +27,12 @@ describe("/store/carts", () => {
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
try {
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
} catch (error) {
console.log(error)
}
})
afterAll(async () => {
@@ -103,6 +107,7 @@ describe("/store/carts", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
@@ -113,7 +118,26 @@ describe("/store/carts", () => {
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 () => {
expect.assertions(2)
const api = useApi()
try {
@@ -128,6 +152,62 @@ describe("/store/carts", () => {
}
})
it("fails to apply expired discount", async () => {
expect.assertions(2)
const api = useApi()
try {
await api.post("/store/carts/test-cart", {
discounts: [{ code: "EXP_DISC" }],
})
} catch (error) {
expect(error.response.status).toEqual(400)
expect(error.response.data.message).toEqual("Discount is expired")
}
})
it("fails on discount before start day", async () => {
expect.assertions(2)
const api = useApi()
try {
await api.post("/store/carts/test-cart", {
discounts: [{ code: "PREM_DISC" }],
})
} catch (error) {
expect(error.response.status).toEqual(400)
expect(error.response.data.message).toEqual("Discount is not valid yet")
}
})
it("fails on apply invalid dynamic discount", async () => {
const api = useApi()
try {
await api.post("/store/carts/test-cart", {
discounts: [{ code: "INV_DYN_DISC" }],
})
} catch (error) {
expect(error.response.status).toEqual(400)
expect(error.response.data.message).toEqual("Discount is expired")
}
})
it("Applies dynamic discount to cart correctly", async () => {
const api = useApi()
const cart = await api.post(
"/store/carts/test-cart",
{
discounts: [{ code: "DYN_DISC" }],
},
{ withCredentials: true }
)
expect(cart.data.cart.shipping_total).toBe(1000)
expect(cart.status).toEqual(200)
})
it("updates cart customer id", async () => {
const api = useApi()
@@ -279,9 +359,82 @@ describe("/store/carts", () => {
expect(e.response.status).toBe(409)
}
//check to see if payment has been cancelled
//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)
})
})
@@ -377,13 +530,15 @@ describe("/store/carts", () => {
)
// Add a 10% discount to the cart
const cartWithGiftcard = await api.post(
"/store/carts/test-cart",
{
discounts: [{ code: "10PERCENT" }],
},
{ withCredentials: true }
)
const cartWithGiftcard = await api
.post(
"/store/carts/test-cart",
{
discounts: [{ code: "10PERCENT" }],
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
// 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)

View File

@@ -5,6 +5,7 @@ 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
@@ -26,6 +27,7 @@ describe("/store/products", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
@@ -261,5 +263,38 @@ describe("/store/products", () => {
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",
}),
])
)
})
})
})

View File

@@ -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",
}),
]);
});

View File

@@ -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",
}),
]);
});
});
});

View File

@@ -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,
});
};
})
}

View File

@@ -17,6 +17,18 @@ const {
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))(
new Date()
)
const tomorrow = ((today) => new Date(today.setDate(today.getDate() + 1)))(
new Date()
)
const tenDaysAgo = ((today) => new Date(today.setDate(today.getDate() - 10)))(
new Date()
)
const tenDaysFromToday = ((today) =>
new Date(today.setDate(today.getDate() + 10)))(new Date())
const manager = connection.manager
const defaultProfile = await manager.findOne(ShippingProfile, {
@@ -39,6 +51,25 @@ module.exports = async (connection, data = {}) => {
currency_code: "usd",
tax_rate: 0,
})
<<<<<<< HEAD
=======
// 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'`
)
>>>>>>> develop
const freeRule = manager.create(DiscountRule, {
id: "free-shipping-rule",
@@ -72,6 +103,11 @@ module.exports = async (connection, data = {}) => {
code: "10PERCENT",
is_dynamic: false,
is_disabled: false,
<<<<<<< HEAD
=======
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
>>>>>>> develop
})
tenPercent.regions = [r]
@@ -92,11 +128,105 @@ module.exports = async (connection, data = {}) => {
value: 10000,
allocation: "total",
})
<<<<<<< HEAD
d.rule = dr
d.regions = [r]
await manager.save(d)
=======
d.rule = dr
d.regions = [r]
await manager.save(d)
const expiredRule = manager.create(DiscountRule, {
id: "expiredRule",
description: "expired rule",
type: "fixed",
value: 100,
allocation: "total",
})
const expiredDisc = manager.create(Discount, {
id: "expiredDisc",
code: "EXP_DISC",
is_dynamic: false,
is_disabled: false,
starts_at: tenDaysAgo,
ends_at: yesterday,
})
expiredDisc.regions = [r]
expiredDisc.rule = expiredRule
await manager.save(expiredDisc)
const prematureRule = manager.create(DiscountRule, {
id: "prematureRule",
description: "premature rule",
type: "fixed",
value: 100,
allocation: "total",
})
const prematureDiscount = manager.create(Discount, {
id: "prematureDiscount",
code: "PREM_DISC",
is_dynamic: false,
is_disabled: false,
starts_at: tomorrow,
ends_at: tenDaysFromToday,
})
prematureDiscount.regions = [r]
prematureDiscount.rule = prematureRule
await manager.save(prematureDiscount)
const invalidDynamicRule = manager.create(DiscountRule, {
id: "invalidDynamicRule",
description: "invalidDynamic rule",
type: "fixed",
value: 100,
allocation: "total",
})
const invalidDynamicDiscount = manager.create(Discount, {
id: "invalidDynamicDiscount",
code: "INV_DYN_DISC",
is_dynamic: true,
is_disabled: false,
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
valid_duration: "P1D", // one day
})
invalidDynamicDiscount.regions = [r]
invalidDynamicDiscount.rule = invalidDynamicRule
await manager.save(invalidDynamicDiscount)
const DynamicRule = manager.create(DiscountRule, {
id: "DynamicRule",
description: "Dynamic rule",
type: "fixed",
value: 10000,
allocation: "total",
})
const DynamicDiscount = manager.create(Discount, {
id: "DynamicDiscount",
code: "DYN_DISC",
is_dynamic: true,
is_disabled: false,
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
valid_duration: "P1M", //one month
})
DynamicDiscount.regions = [r]
DynamicDiscount.rule = DynamicRule
await manager.save(DynamicDiscount)
>>>>>>> develop
await manager.query(
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
@@ -259,6 +389,28 @@ module.exports = async (connection, data = {}) => {
completed_at: null,
items: [],
})
<<<<<<< HEAD
=======
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",
},
})
>>>>>>> develop
const pay = manager.create(Payment, {
id: "test-payment",
@@ -268,12 +420,38 @@ module.exports = async (connection, data = {}) => {
provider_id: "test-pay",
data: {},
})
<<<<<<< HEAD
await manager.save(pay)
cart2.payment = pay
await manager.save(cart2)
=======
await manager.save(pay)
cart2.payment = pay
await manager.save(cart2)
const swapPay = manager.create(Payment, {
id: "test-swap-payment",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test-pay",
data: {},
})
await manager.save(pay)
await manager.save(swapPay)
cart2.payment = pay
swapCart.payment = swapPay
await manager.save(cart2)
await manager.save(swapCart)
>>>>>>> develop
await manager.insert(PaymentSession, {
id: "test-session",
@@ -283,6 +461,18 @@ module.exports = async (connection, data = {}) => {
data: {},
status: "authorized",
})
<<<<<<< HEAD
=======
await manager.insert(PaymentSession, {
id: "test-swap-session",
cart_id: "swap-cart",
provider_id: "test-pay",
is_selected: true,
data: {},
status: "authorized",
})
>>>>>>> develop
await manager.insert(ShippingMethod, {
id: "test-method",

View File

@@ -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",
});
};
})
}

View File

@@ -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,
})
}

View File

@@ -14,7 +14,6 @@ const {
Swap,
Cart,
Return,
RMAShippingOption,
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
@@ -56,7 +55,7 @@ module.exports = async (connection, data = {}) => {
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",
@@ -76,7 +75,7 @@ module.exports = async (connection, data = {}) => {
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,
@@ -95,12 +94,13 @@ 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)
<<<<<<< HEAD
const rmaCart = manager.create(Cart, {
id: "test-cart-rma",
@@ -154,6 +154,8 @@ module.exports = async (connection, data = {}) => {
})
await manager.save(swapWithRMAMethod)
=======
>>>>>>> develop
const cartTemplate = async (cartId) => {
const cart = manager.create(Cart, {
@@ -253,7 +255,10 @@ module.exports = async (connection, data = {}) => {
order_id: orderWithSwap.id,
item_id: li.id,
refund_amount: li.quantity * li.unit_price,
<<<<<<< HEAD
// shipping_method_id: ,
=======
>>>>>>> develop
})
await manager.save(swapReturn)
@@ -268,7 +273,7 @@ module.exports = async (connection, data = {}) => {
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: {},
})

View File

@@ -8,15 +8,15 @@
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "1.1.41-dev-1632959007011",
"medusa-interfaces": "1.1.23-dev-1632959007011",
"@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.15-dev-1632959007011",
"babel-preset-medusa-package": "1.1.13-dev-1631630701835",
"jest": "^26.6.3"
}
}

View File

@@ -1223,10 +1223,10 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@medusajs/medusa-cli@1.1.18-dev-1632959007011":
version "1.1.18-dev-1632959007011"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.18-dev-1632959007011.tgz#015c1c783a66e6ce7304f38ccee45e805c828885"
integrity sha512-6Aa9coormjBoAcQsymBCJXbtwHIF+IXovjq0C7CbwS58oytzJiReB+WJQmK1/tMq79femCIzH6TCLAqWkZCpWg==
"@medusajs/medusa-cli@1.1.16-dev-1631630701835":
version "1.1.16-dev-1631630701835"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.16-dev-1631630701835.tgz#7fcb95cb9a45e0367cc5becfff7f5d1533b46b5f"
integrity sha512-UomtR8B1lBFDb3h1y060fOcWcZi812Jwt8Kgjxqtpn+aRj6Bu7+I3WJGHBVSx4VnUBINSYbtiQMpEwqVGTCKnw==
dependencies:
"@babel/polyfill" "^7.8.7"
"@babel/runtime" "^7.9.6"
@@ -1244,8 +1244,8 @@
is-valid-path "^0.1.1"
joi-objectid "^3.0.1"
meant "^1.0.1"
medusa-core-utils "1.1.22-dev-1632959007011"
medusa-telemetry "0.0.5-dev-1632959007011"
medusa-core-utils "1.1.20-dev-1631630701835"
medusa-telemetry "0.0.3-dev-1631630701835"
netrc-parser "^3.1.6"
open "^8.0.6"
ora "^5.4.1"
@@ -1259,13 +1259,13 @@
winston "^3.3.3"
yargs "^15.3.1"
"@medusajs/medusa@1.1.41-dev-1632959007011":
version "1.1.41-dev-1632959007011"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.41-dev-1632959007011.tgz#be73b1d7bc3af918130f7dff7883c6badda68a50"
integrity sha512-IVmz2U329dUTvC79yhxiH4mwEejvaTlecpM68uGQfNlU0lfONJQDNAzrLoBeYsrVI1okmVWTuDVvg22UE4Mc3A==
"@medusajs/medusa@1.1.40-dev-1631630701835":
version "1.1.40-dev-1631630701835"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.40-dev-1631630701835.tgz#fa67ceda5887fd31196b3bcfd3115a9e02d68448"
integrity sha512-svPsKonuBrwRgtYod7U7ho9bN84K7N/QorMJG9+wklEO4jp6zXG+U5DQcfVAKQ00cHHe50OcnfX1ZS0kVNovYw==
dependencies:
"@hapi/joi" "^16.1.8"
"@medusajs/medusa-cli" "1.1.18-dev-1632959007011"
"@medusajs/medusa-cli" "1.1.16-dev-1631630701835"
"@types/lodash" "^4.14.168"
awilix "^4.2.3"
body-parser "^1.19.0"
@@ -1286,8 +1286,8 @@
joi "^17.3.0"
joi-objectid "^3.0.1"
jsonwebtoken "^8.5.1"
medusa-core-utils "1.1.22-dev-1632959007011"
medusa-test-utils "1.1.25-dev-1632959007011"
medusa-core-utils "1.1.20-dev-1631630701835"
medusa-test-utils "1.1.23-dev-1631630701835"
morgan "^1.9.1"
multer "^1.4.2"
passport "^0.4.0"
@@ -1933,10 +1933,10 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
babel-preset-medusa-package@1.1.15-dev-1632959007011:
version "1.1.15-dev-1632959007011"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15-dev-1632959007011.tgz#fa8d0e46ab52761446c03452bbc00c0eed5ca1b8"
integrity sha512-3E4NjDP/9OUS9DW5aCV6bcYwLYpa3xRIlR2q8WTZMaM+jsIxSuA7+HD1tbmDrxMpRB9ErYPhX6iyKZMzvHKw6w==
babel-preset-medusa-package@1.1.13-dev-1631630701835:
version "1.1.13-dev-1631630701835"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.13-dev-1631630701835.tgz#5b66b3738e4904e31b2db30a6ea8e68eb0f8f641"
integrity sha512-V7sXlktlvEON7FLhxe+Y3NVe8l8DQyB5oJTryG4Bhw8y1AaUFOiQ5Vat3XuoL3qRcUSVMGL4VHw0m0O78t0PuA==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.12.1"
"@babel/plugin-proposal-decorators" "^7.12.1"
@@ -5105,25 +5105,25 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@1.1.22-dev-1632959007011:
version "1.1.22-dev-1632959007011"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.22-dev-1632959007011.tgz#41209056f0a1a47503d8fc1d0c5e30e49811bdd9"
integrity sha512-jRfRWMxiNDYwTQ6k9Lers7HWqTfI5ZjIihRKLIx+mGio/mOFVr243co7VIZXOUurL7BM3ddm2HP87nZTz68qBA==
medusa-core-utils@1.1.20-dev-1631630701835:
version "1.1.20-dev-1631630701835"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.20-dev-1631630701835.tgz#1fa7ccd2551b7891127d4f07f708029c585f4ea8"
integrity sha512-KKBo6W1QI47Ig3KMV4UXQnQN5JilMfjR6Cx7hDNj4frJoNiWa/YKDYqUr6SmY2+iJtKetnLkrKaPsDyyhZrxcw==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.1.23-dev-1632959007011:
version "1.1.23-dev-1632959007011"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.23-dev-1632959007011.tgz#1e8cfc25d2c2363c370e3580b1f18bfa818b0569"
integrity sha512-HyyY6FnUtvzw0w9/4H8dEfC6LcAkYNoxep9Rh0HZBwchrZUx/JaSUAmRWFSzHaaqjxk6txbZIVjerAj2t2YBRQ==
medusa-interfaces@1.1.21-dev-1631630701835:
version "1.1.21-dev-1631630701835"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.21-dev-1631630701835.tgz#af29b2ef0c987bded1b2d295ac6cf39880af551e"
integrity sha512-rTASRjOdcS3J9fP95p9vJzCpatMpUhTum5ddfAA0s42pZx2gsPlf1f+rUSNz5QfeC5RdIEzRfOmAGfvMpAbYGw==
dependencies:
medusa-core-utils "1.1.22-dev-1632959007011"
medusa-core-utils "1.1.20-dev-1631630701835"
medusa-telemetry@0.0.5-dev-1632959007011:
version "0.0.5-dev-1632959007011"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.5-dev-1632959007011.tgz#b10a46693403140bf3d5963010d6c640340a3f33"
integrity sha512-BqEb1MHz8ac1vMH0miIXNBbTkvvUvcy2WkoCVUnG4zzkL4cApXqUKitMUMT774kh2WO/glOnIccNNlKbAJgaZg==
medusa-telemetry@0.0.3-dev-1631630701835:
version "0.0.3-dev-1631630701835"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.3-dev-1631630701835.tgz#d56c01d261fa30ccedc6d9976971b9744b9d8c0f"
integrity sha512-FS1L1DOIOSdRZgeIQWaM5nhFG5NtbnC/Pntfac51vQxLkzFuHy7ZEtg11CXKE+x6NWlqT1rqqgxq0EabFzEZzw==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
@@ -5135,13 +5135,13 @@ medusa-telemetry@0.0.5-dev-1632959007011:
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
medusa-test-utils@1.1.25-dev-1632959007011:
version "1.1.25-dev-1632959007011"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.25-dev-1632959007011.tgz#c3d971e89d52d35898ab7d95c2a449846ce1cd38"
integrity sha512-wCKlIugeRlgJIaoyYN5K9KtJaBd66rdmg1yD5Vxq+Fd5rftJUOP2wiN5nIV0BK5v13mMH56iSY7aOK12Um287Q==
medusa-test-utils@1.1.23-dev-1631630701835:
version "1.1.23-dev-1631630701835"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.23-dev-1631630701835.tgz#8995d636caf2dea9ebb184f1e15b0c364c4d1b93"
integrity sha512-A8xRL+sZS22qXZSHpVfdV8f/egZxXs4iExRO2xUkTP6I/OgMhFBSg6nEd/DXVdVfpsHZCDEv8PA3ewaeAkoYhQ==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "1.1.22-dev-1632959007011"
medusa-core-utils "1.1.20-dev-1631630701835"
randomatic "^3.1.1"
merge-descriptors@1.0.1:

View File

@@ -1,16 +1,16 @@
const path = require('path');
const {dropDatabase} = require('pg-god');
const path = require("path")
const { dropDatabase } = require("pg-god")
require('dotenv').config({path: path.join(__dirname, '.env')});
require("dotenv").config({ path: path.join(__dirname, ".env") })
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 || ""
const pgGodCredentials = {
user: DB_USERNAME,
password: DB_PASSWORD,
};
}
afterAll(() => {
dropDatabase({databaseName: 'medusa-integration'}, pgGodCredentials);
});
dropDatabase({ databaseName: "medusa-integration" }, pgGodCredentials)
})

View File

@@ -2246,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"
@@ -2870,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"
@@ -4091,16 +4104,17 @@ medusa-core-utils@^0.1.27:
"@hapi/joi" "^16.1.8"
joi-objectid "^3.0.1"
medusa-telemetry@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.3.tgz#c11e5e0f3cc969f3eaee41d1c24f78a5c0715362"
integrity sha512-Qb/sgOwO8t2Sjjo4nKyBa6hKZ/SjniT4eEWenygEaJDqXZhfogVYGhWc5gn4tLlFFNEHXzDTlrqX2LvzfEJWIw==
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"
@@ -4167,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"
@@ -4706,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"
@@ -5561,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"

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"

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"]
}
}
}

View File

@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}

16
packages/medusa-file-s3/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
/__mocks__

View File

@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock

View File

@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@@ -0,0 +1 @@
// noop

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"
}
}

View File

@@ -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

View File

@@ -3,6 +3,14 @@
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

View File

@@ -1,6 +1,6 @@
{
"name": "medusa-payment-stripe",
"version": "1.1.25",
"version": "1.1.26",
"description": "Stripe Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {

View File

@@ -333,7 +333,7 @@ class StripeProviderService extends PaymentService {
async cancelPayment(payment) {
const { id } = payment.data
try {
return this.stripe_.paymentIntents.cancel(id)
return await this.stripe_.paymentIntents.cancel(id)
} catch (error) {
if (error.payment_intent.status === "canceled") {
return error.payment_intent

View File

@@ -857,6 +857,18 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@hapi/hoek@^9.0.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131"
integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1058,6 +1070,23 @@
readdirp "^2.2.1"
upath "^1.1.1"
"@sideway/address@^4.1.0":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1"
integrity sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinonjs/commons@^1.7.0":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b"
@@ -3167,6 +3196,22 @@ jest@^25.5.2:
import-local "^3.0.2"
jest-cli "^25.5.4"
joi-objectid@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
joi@^17.3.0:
version "17.4.2"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7"
integrity sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.0"
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3379,6 +3424,14 @@ math-random@^1.0.1:
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
medusa-core-utils@^1.1.22:
version "1.1.22"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.22.tgz#84ce0af0a7c672191d758ea462056e30a39d08b1"
integrity sha512-kMuRkWOuNG4Bw6epg/AYu95UJuE+rjHTeTWRLbEPrYGjWREV82tLWVDI21/QcccmaHmMU98Rkw2z9JwyFZIiyw==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -4516,9 +4569,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"

View File

@@ -63,6 +63,7 @@
"glob": "^7.1.6",
"ioredis": "^4.17.3",
"ioredis-mock": "^5.6.0",
"iso8601-duration": "^1.3.0",
"joi": "^17.3.0",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",

View File

@@ -12,6 +12,9 @@ import { Validator, MedusaError } from "medusa-core-utils"
* application/json:
* schema:
* properties:
* email:
* type: string
* description: The Customer's email. Only providable if user not registered.
* first_name:
* type: string
* description: The Customer's first name.
@@ -37,6 +40,7 @@ export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
email: Validator.string().optional(),
first_name: Validator.string().optional(),
last_name: Validator.string().optional(),
password: Validator.string().optional(),
@@ -50,9 +54,19 @@ export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
let customer = await customerService.retrieve(id)
if (value.email && customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email cannot be changed when the user has registered their account"
)
}
await customerService.update(id, value)
const customer = await customerService.retrieve(id, {
customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })

View File

@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}

View File

@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}

View File

@@ -16,6 +16,8 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
ends_at: "03/14/2021 04:30",
},
adminSession: {
jwt: {
@@ -39,12 +41,99 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
ends_at: new Date("03/14/2021 04:30"),
is_disabled: false,
is_dynamic: false,
})
})
})
describe("unsuccessful creation with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful creation with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_disabled: false,
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
})
})
})
describe("fails on invalid data", () => {
let subject
@@ -74,4 +163,84 @@ describe("POST /admin/discounts", () => {
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
describe("succesfully creates a dynamic discount without setting valid duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
is_dynamic: true,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "03/14/2021 14:30",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns error", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
is_dynamic: true,
is_disabled: false,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("03/14/2021 14:30"),
})
})
})
})

View File

@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [

View File

@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [

View File

@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [

View File

@@ -7,6 +7,7 @@ describe("POST /admin/discounts", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
@@ -50,4 +51,139 @@ describe("POST /admin/discounts", () => {
)
})
})
describe("unsuccessful update with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful update with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service update", () => {
expect(DiscountServiceMock.update).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("total10"),
{
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
}
)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
})

View File

@@ -71,7 +71,13 @@ export default async (req, res) => {
.required(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
valid_duration: Validator.string()
.isoDuration()
.allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),

View File

@@ -37,9 +37,9 @@ export default async (req, res) => {
try {
const discountService = req.scope.resolve("discountService")
await discountService.createDynamicCode(discount_id, value)
const created = await discountService.createDynamicCode(discount_id, value)
const discount = await discountService.retrieve(discount_id, {
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
})

View File

@@ -74,6 +74,7 @@ export const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
export const defaultRelations = [

View File

@@ -68,7 +68,16 @@ export default async (req, res) => {
.optional(),
is_disabled: Validator.boolean().optional(),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.when("starts_at", {
not: undefined,
then: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
otherwise: Validator.date().optional(),
}),
valid_duration: Validator.string()
.isoDuration().allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
@@ -78,6 +87,7 @@ export default async (req, res) => {
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}

View File

@@ -22,6 +22,7 @@ import variantRoutes from "./variants"
import draftOrderRoutes from "./draft-orders"
import collectionRoutes from "./collections"
import notificationRoutes from "./notifications"
import noteRoutes from "./notes"
const route = Router()
@@ -68,6 +69,7 @@ export default (app, container, config) => {
collectionRoutes(route)
notificationRoutes(route)
returnReasonRoutes(route)
noteRoutes(route)
return app
}

View File

@@ -0,0 +1,63 @@
import { MedusaError, Validator } from "medusa-core-utils"
/**
* @oas [post] /notes
* operationId: "PostNotes"
* summary: "Creates a Note"
* description: "Creates a Note which can be associated with any resource as required."
* requestBody:
* content:
* application/json:
* schema:
* properties:
* resource_id:
* type: string
* description: The id of the resource which the Note relates to.
* resource_type:
* type: string
* description: The type of resource which the Note relates to.
* value:
* type: string
* description: The content of the Note to create.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*
*/
export default async (req, res) => {
const schema = Validator.object().keys({
resource_id: Validator.string(),
resource_type: Validator.string(),
value: Validator.string(),
})
const userId = req.user.id || req.user.userId
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const noteService = req.scope.resolve("noteService")
const result = await noteService.create({
resource_id: value.resource_id,
resource_type: value.resource_type,
value: value.value,
author_id: userId,
})
res.status(200).json({ note: result })
} catch (err) {
throw err
}
}

View File

@@ -0,0 +1,35 @@
/**
* @oas [delete] /notes/{id}
* operationId: "DeleteNotesNote"
* summary: "Deletes a Note"
* description: "Deletes a Note."
* parameters:
* - (path) id=* {string} The id of the Note to delete.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted Note.
* deleted:
* type: boolean
* description: Whether or not the Note was deleted.
*/
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
await noteService.delete(id)
res.status(200).json({ id, deleted: true })
} catch (err) {
throw err
}
}

View File

@@ -0,0 +1,31 @@
/**
* @oas [get] /notes/{id}
* operationId: "GetNoteNote"
* summary: "Get Note"
* description: "Retrieves a single note using its id"
* parameters:
* - (path) id=* {string} The id of the note to retrieve.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
const note = await noteService.retrieve(id, { relations: ["author"] })
res.status(200).json({ note })
} catch (err) {
throw err
}
}

View File

@@ -0,0 +1,20 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/notes", route)
route.get("/:id", middlewares.wrap(require("./get-note").default))
route.get("/", middlewares.wrap(require("./list-notes").default))
route.post("/", middlewares.wrap(require("./create-note").default))
route.post("/:id", middlewares.wrap(require("./update-note").default))
route.delete("/:id", middlewares.wrap(require("./delete-note").default))
return app
}

View File

@@ -0,0 +1,42 @@
/**
* @oas [get] /notes
* operationId: "GetNotes"
* summary: "List Notes"
* description: "Retrieves a list of notes"
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notes:
* type: array
* items:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
try {
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
const selector = {}
if ("resource_id" in req.query) {
selector.resource_id = req.query.resource_id
}
const noteService = req.scope.resolve("noteService")
const notes = await noteService.list(selector, {
take: limit,
skip: offset,
relations: ["author"],
})
res.status(200).json({ notes })
} catch (err) {
throw err
}
}

View File

@@ -0,0 +1,51 @@
import { MedusaError, Validator } from "medusa-core-utils"
/**
* @oas [post] /notes/{id}
* operationId: "PostNotesNote"
* summary: "Updates a Note"
* description: "Updates a Note associated with some resource"
* parameters:
* - (path) id=* {string} The id of the Note to update
* requestBody:
* content:
* application/json:
* schema:
* properties:
* value:
* type: string
* description: The updated description of the Note.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*
*/
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
value: Validator.string(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const noteService = req.scope.resolve("noteService")
const result = await noteService.update(id, value.value)
res.status(200).json({ note: result })
} catch (err) {
throw err
}
}

View File

@@ -48,6 +48,9 @@ import { defaultFields, defaultRelations } from "./"
* no_notification:
* description: If set to true no notification will be send related to this Swap.
* type: boolean
* allow_backorder:
* description: If true, swaps can be completed with items out of stock
* type: boolean
* tags:
* - Order
* responses:
@@ -89,6 +92,7 @@ export default async (req, res) => {
quantity: Validator.number().required(),
}),
no_notification: Validator.boolean().optional(),
allow_backorder: Validator.boolean().default(true),
})
const { value, error } = schema.validate(req.body)
@@ -148,6 +152,7 @@ export default async (req, res) => {
{
idempotency_key: idempotencyKey.idempotency_key,
no_notification: value.no_notification,
allow_backorder: value.allow_backorder,
}
)

View File

@@ -110,6 +110,7 @@ describe("POST /admin/products", () => {
description: "Test Description",
tags: [{ id: "test", value: "test" }],
handle: "test-product",
status: "draft",
is_giftcard: false,
options: [{ title: "Denominations" }],
profile_id: IdMap.getId("default_shipping_profile"),
@@ -170,6 +171,7 @@ describe("POST /admin/products", () => {
options: [{ title: "Denominations" }],
handle: "test-gift-card",
is_giftcard: true,
status: "draft",
profile_id: IdMap.getId("giftCardProfile"),
})
})

View File

@@ -193,6 +193,9 @@ export default async (req, res) => {
.optional(),
thumbnail: Validator.string().optional(),
handle: Validator.string().optional(),
status: Validator.string()
.valid("proposed", "draft", "published", "rejected")
.default("draft"),
type: Validator.object()
.keys({
id: Validator.string().optional(),

View File

@@ -1,4 +1,5 @@
import _ from "lodash"
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
/**
@@ -56,6 +57,20 @@ export default async (req, res) => {
selector.is_giftcard = req.query.is_giftcard === "true"
}
if ("status" in req.query) {
const schema = Validator.array()
.items(
Validator.string().valid("proposed", "draft", "published", "rejected")
)
.single()
const { value, error } = schema.validate(req.query.status)
if (value && !error) {
selector.status = value
}
}
const listConfig = {
select: includeFields.length ? includeFields : defaultFields,
relations: expandFields.length ? expandFields : defaultRelations,

View File

@@ -193,6 +193,12 @@ export default async (req, res) => {
.allow(null, ""),
description: Validator.string().optional(),
discountable: Validator.boolean().optional(),
status: Validator.string().valid(
"proposed",
"draft",
"published",
"rejected"
),
type: Validator.object()
.keys({
id: Validator.string().optional(),

View File

@@ -39,6 +39,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
value: Validator.string().required(),
label: Validator.string().required(),
parent_return_reason_id: Validator.string().optional(),
description: Validator.string()
.optional()
.allow(""),

View File

@@ -0,0 +1,41 @@
/**
* @oas [delete] /return-reason/{id}
* operationId: "DeleteReturnReason"
* summary: "Delete a return reason"
* description: "Deletes a return reason."
* parameters:
* - (path) id=* {string} The id of the return reason
* tags:
* - Return Reason
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted return reason
* object:
* type: string
* description: The type of the object that was deleted.
* deleted:
* type: boolean
*/
export default async (req, res) => {
const { id } = req.params
try {
const returnReasonService = req.scope.resolve("returnReasonService")
await returnReasonService.delete(id)
res.json({
id: id,
object: "return_reason",
deleted: true,
})
} catch (err) {
throw err
}
}

View File

@@ -26,6 +26,11 @@ export default app => {
*/
route.post("/:id", middlewares.wrap(require("./update-reason").default))
/**
* Delete a reason
*/
route.delete("/:id", middlewares.wrap(require("./delete-reason").default))
return app
}
@@ -33,10 +38,14 @@ export const defaultFields = [
"id",
"value",
"label",
"parent_return_reason_id",
"description",
"created_at",
"updated_at",
"deleted_at",
]
export const defaultRelations = []
export const defaultRelations = [
"parent_return_reason",
"return_reason_children",
]

View File

@@ -24,7 +24,7 @@ export default async (req, res) => {
try {
const returnReasonService = req.scope.resolve("returnReasonService")
const query = {}
const query = { parent_return_reason_id: null }
const data = await returnReasonService.list(query, {
select: defaultFields,
relations: defaultRelations,

View File

@@ -42,6 +42,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
label: Validator.string().optional(),
parent_return_reason_id: Validator.string().optional(),
description: Validator.string()
.optional()
.allow(""),

View File

@@ -138,18 +138,36 @@ export default async (req, res) => {
// If cart is part of swap, we register swap as complete
switch (cart.type) {
case "swap": {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
try {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
}
} catch (error) {
if (
error &&
error.code === MedusaError.Codes.INSUFFICIENT_INVENTORY
) {
return {
response_code: 409,
response_body: {
message: error.message,
type: error.type,
code: error.code,
},
}
} else {
throw error
}
}
}
// case "payment_link":

View File

@@ -11,6 +11,7 @@ describe("POST /store/customers/:id", () => {
payload: {
first_name: "LeBron",
last_name: "James",
email: "test@email.com",
},
clientSession: {
jwt: {
@@ -31,6 +32,7 @@ describe("POST /store/customers/:id", () => {
{
first_name: "LeBron",
last_name: "James",
email: "test@email.com",
}
)
})

View File

@@ -1,3 +1,4 @@
import { optional } from "joi"
import { Validator, MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
@@ -27,6 +28,9 @@ import { defaultRelations, defaultFields } from "./"
* phone:
* description: "The Customer's phone number."
* type: string
* email:
* description: "The email of the customer."
* type: string
* metadata:
* description: "Metadata about the customer."
* type: object
@@ -51,6 +55,7 @@ export default async (req, res) => {
last_name: Validator.string().optional(),
password: Validator.string().optional(),
phone: Validator.string().optional(),
email: Validator.string().optional(),
metadata: Validator.object().optional(),
})

View File

@@ -18,7 +18,7 @@ describe("GET /store/products", () => {
it("calls get product from productSerice", () => {
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.list).toHaveBeenCalledWith(
{},
{ status: ["published"] },
{ relations: defaultRelations, skip: 0, take: 100 }
)
})
@@ -43,7 +43,7 @@ describe("GET /store/products", () => {
it("calls list from productSerice", () => {
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.list).toHaveBeenCalledWith(
{ is_giftcard: true },
{ is_giftcard: true, status: ["published"] },
{ relations: defaultRelations, skip: 0, take: 100 }
)
})

View File

@@ -1,3 +1,4 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
/**
@@ -41,6 +42,8 @@ export default async (req, res) => {
selector.is_giftcard = req.query.is_giftcard === "true"
}
selector.status = ["published"]
const listConfig = {
relations: defaultRelations,
skip: offset,

View File

@@ -23,10 +23,14 @@ export const defaultFields = [
"id",
"value",
"label",
"parent_return_reason_id",
"description",
"created_at",
"updated_at",
"deleted_at",
]
export const defaultRelations = []
export const defaultRelations = [
"parent_return_reason",
"return_reason_children",
]

View File

@@ -24,7 +24,7 @@ export default async (req, res) => {
try {
const returnReasonService = req.scope.resolve("returnReasonService")
const query = {}
const query = { parent_return_reason_id: null}
const data = await returnReasonService.list(query, {
select: defaultFields,
relations: defaultRelations,

View File

@@ -28,10 +28,7 @@ describe("Get variant by id", () => {
describe("get variant with prices", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/store/variants/${IdMap.getId("variantWithPrices")}`
)
subject = await request("GET", `/store/variants/variant_with_prices`)
})
it("successfully retrieves variants with prices", async () => {
expect(subject.status).toEqual(200)

View File

@@ -114,6 +114,9 @@ const t = async function({ directory, migrate, seedFile }) {
const variants = p.variants
delete p.variants
// default to the products being visible
p.status = p.status || "published"
p.profile_id = defaultProfile.id
if (p.is_giftcard) {
p.profile_id = gcProfile.id

View File

@@ -45,4 +45,5 @@ export { Swap } from "./models/swap"
export { User } from "./models/user"
export { DraftOrder } from "./models/draft-order"
export { ReturnReason } from "./models/return-reason"
export { Note } from "./models/note"
export { RMAShippingOption } from "./models/rma-shipping-option"

View File

@@ -0,0 +1,24 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class allowBackorderSwaps1630505790603 implements MigrationInterface {
name = 'allowBackorderSwaps1630505790603'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "swap" ADD "allow_backorder" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "cart" ADD "payment_authorized_at" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum" RENAME TO "swap_payment_status_enum_old"`);
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum" AS ENUM('not_paid', 'awaiting', 'captured', 'confirmed', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum" USING "payment_status"::"text"::"swap_payment_status_enum"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum_old"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum_old" AS ENUM('not_paid', 'awaiting', 'captured', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum_old" USING "payment_status"::"text"::"swap_payment_status_enum_old"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum_old" RENAME TO "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "payment_authorized_at"`);
await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "allow_backorder"`);
}
}

View File

@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class validDurationForDiscount1631696624528 implements MigrationInterface {
name = 'validDurationForDiscount1631696624528'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "discount" ADD "valid_duration" character varying`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "discount" DROP COLUMN "valid_duration"`);
}
}

View File

@@ -0,0 +1,20 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class nestedReturnReasons1631800727788 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "return_reason" ADD "parent_return_reason_id" character varying`
)
await queryRunner.query(`ALTER TABLE "return_reason" ADD CONSTRAINT "FK_2250c5d9e975987ab212f61a657" FOREIGN KEY ("parent_return_reason_id") REFERENCES "return_reason"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "return_reason" DROP COLUMN "parent_return_reason_id"`
)
}
}

View File

@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class statusOnProduct1631864388026 implements MigrationInterface {
name = "statusOnProduct1631864388026"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "product_status_enum" AS ENUM('draft', 'proposed', 'published', 'rejected')`
)
await queryRunner.query(
`ALTER TABLE "product" ADD "status" "product_status_enum" `
)
await queryRunner.query(
`UPDATE "product" SET "status" = 'published' WHERE "status" IS NULL`
)
await queryRunner.query(
`ALTER TABLE "product" ALTER COLUMN "status" SET NOT NULL`
)
await queryRunner.query(
`ALTER TABLE "product" ALTER COLUMN "status" SET DEFAULT 'draft'`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "product" DROP COLUMN "status"`)
await queryRunner.query(`DROP TYPE "product_status_enum"`)
}
}

View File

@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class addNotes1632220294687 implements MigrationInterface {
name = "addNotes1632220294687"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "note" ("id" character varying NOT NULL, "value" character varying NOT NULL, "resource_type" character varying NOT NULL, "resource_id" character varying NOT NULL, "author_id" character varying, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE INDEX "IDX_f74980b411cf94af523a72af7d" ON "note" ("resource_type") `
)
await queryRunner.query(
`CREATE INDEX "IDX_3287f98befad26c3a7dab088cf" ON "note" ("resource_id") `
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_3287f98befad26c3a7dab088cf"`)
await queryRunner.query(`DROP INDEX "IDX_f74980b411cf94af523a72af7d"`)
await queryRunner.query(`DROP TABLE "note"`)
}
}

View File

@@ -231,6 +231,9 @@ export class Cart {
@Column({ type: resolveDbType("timestamptz"), nullable: true })
completed_at: Date
@Column({ type: resolveDbType("timestamptz"), nullable: true })
payment_authorized_at: Date
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date

View File

@@ -46,7 +46,7 @@ export class Customer {
)
shipping_addresses: Address[]
@Column({ nullable: true })
@Column({ nullable: true, select: false })
password_hash: string
@Column({ nullable: true })

Some files were not shown because too many files have changed in this diff Show More